Browse Source

add session recording and playback

David Rose 22 years ago
parent
commit
889be29b47
100 changed files with 3022 additions and 424 deletions
  1. 2 0
      direct/src/distributed/ClientRepository.py
  2. 35 0
      direct/src/distributed/ConnectionRepository.py
  3. 19 11
      direct/src/showbase/EventManager.py
  4. 38 0
      direct/src/showbase/ShowBase.py
  5. 3 3
      panda/metalibs/panda/Sources.pp
  6. 1 1
      panda/metalibs/pandaexpress/Sources.pp
  7. 4 4
      panda/src/collide/collisionEntry.h
  8. 3 3
      panda/src/device/trackerNode.cxx
  9. 1 1
      panda/src/device/trackerNode.h
  10. 5 1
      panda/src/dgraph/config_dgraph.cxx
  11. 23 0
      panda/src/dgraph/dataNode.cxx
  12. 6 0
      panda/src/dgraph/dataNode.h
  13. 0 9
      panda/src/dgraph/dataNodeTransmit.I
  14. 101 0
      panda/src/dgraph/dataNodeTransmit.cxx
  15. 35 2
      panda/src/dgraph/dataNodeTransmit.h
  16. 10 4
      panda/src/display/graphicsWindow.h
  17. 1 1
      panda/src/downloader/Sources.pp
  18. 20 12
      panda/src/event/Sources.pp
  19. 20 0
      panda/src/event/buttonEvent.I
  20. 94 0
      panda/src/event/buttonEvent.cxx
  21. 10 0
      panda/src/event/buttonEvent.h
  22. 0 0
      panda/src/event/buttonEventList.I
  23. 163 0
      panda/src/event/buttonEventList.cxx
  24. 13 0
      panda/src/event/buttonEventList.h
  25. 8 1
      panda/src/event/config_event.cxx
  26. 1 1
      panda/src/event/config_event.h
  27. 1 1
      panda/src/event/event.h
  28. 2 2
      panda/src/event/eventHandler.I
  29. 65 5
      panda/src/event/eventParameter.I
  30. 1 1
      panda/src/event/eventParameter.cxx
  31. 33 19
      panda/src/event/eventParameter.h
  32. 2 1
      panda/src/event/event_composite1.cxx
  33. 25 0
      panda/src/express/datagram.I
  34. 17 0
      panda/src/express/datagram.h
  35. 27 0
      panda/src/express/datagramIterator.I
  36. 16 0
      panda/src/express/datagramIterator.h
  37. 13 5
      panda/src/express/dcast.cxx
  38. 1 1
      panda/src/express/typedReferenceCount.h
  39. 1 1
      panda/src/framework/Sources.pp
  40. 4 0
      panda/src/framework/config_framework.cxx
  41. 3 0
      panda/src/framework/config_framework.h
  42. 35 0
      panda/src/framework/pandaFramework.I
  43. 23 0
      panda/src/framework/pandaFramework.cxx
  44. 6 0
      panda/src/framework/pandaFramework.h
  45. 9 0
      panda/src/framework/windowFramework.cxx
  46. 4 4
      panda/src/framework/windowFramework.h
  47. 3 0
      panda/src/gobj/config_gobj.cxx
  48. 47 0
      panda/src/gobj/lens.cxx
  49. 10 4
      panda/src/gobj/lens.h
  50. 31 0
      panda/src/gobj/matrixLens.cxx
  51. 10 4
      panda/src/gobj/matrixLens.h
  52. 31 0
      panda/src/gobj/orthographicLens.cxx
  53. 6 0
      panda/src/gobj/orthographicLens.h
  54. 31 0
      panda/src/gobj/perspectiveLens.cxx
  55. 6 0
      panda/src/gobj/perspectiveLens.h
  56. 36 0
      panda/src/linmath/lmat_ops_src.I
  57. 9 0
      panda/src/linmath/lmat_ops_src.h
  58. 19 0
      panda/src/linmath/lvec2_ops_src.I
  59. 5 0
      panda/src/linmath/lvec2_ops_src.h
  60. 18 0
      panda/src/linmath/lvec3_ops_src.I
  61. 5 0
      panda/src/linmath/lvec3_ops_src.h
  62. 18 0
      panda/src/linmath/lvec4_ops_src.I
  63. 5 0
      panda/src/linmath/lvec4_ops_src.h
  64. 4 0
      panda/src/mathutil/config_mathutil.cxx
  65. 2 0
      panda/src/pgraph/bamFile.h
  66. 0 1
      panda/src/pgraph/config_pgraph.cxx
  67. 2 3
      panda/src/pgraph/lensNode.cxx
  68. 36 0
      panda/src/pgraph/pandaNode.cxx
  69. 2 0
      panda/src/pgraph/pandaNode.h
  70. 0 31
      panda/src/pgraph/transformState.I
  71. 0 11
      panda/src/pgraph/transformState.cxx
  72. 0 34
      panda/src/pgraph/transformState.h
  73. 8 8
      panda/src/pgui/pgMouseWatcherParameter.h
  74. 5 9
      panda/src/putil/Sources.pp
  75. 3 2
      panda/src/putil/bamReader.I
  76. 161 16
      panda/src/putil/bamReader.cxx
  77. 21 1
      panda/src/putil/bamReader.h
  78. 107 21
      panda/src/putil/bamWriter.cxx
  79. 18 4
      panda/src/putil/bamWriter.h
  80. 0 45
      panda/src/putil/buttonEvent.cxx
  81. 0 74
      panda/src/putil/buttonEventList.cxx
  82. 2 2
      panda/src/putil/config_util.cxx
  83. 11 25
      panda/src/putil/datagramInputFile.cxx
  84. 4 5
      panda/src/putil/datagramOutputFile.cxx
  85. 0 20
      panda/src/putil/modifierButtons.I
  86. 0 2
      panda/src/putil/modifierButtons.h
  87. 0 2
      panda/src/putil/putil_composite1.cxx
  88. 9 1
      panda/src/putil/typedWritable.cxx
  89. 11 1
      panda/src/putil/typedWritable.h
  90. 1 4
      panda/src/putil/typedWritableReferenceCount.h
  91. 38 0
      panda/src/recorder/Sources.pp
  92. 47 0
      panda/src/recorder/config_recorder.cxx
  93. 29 0
      panda/src/recorder/config_recorder.h
  94. 297 0
      panda/src/recorder/mouseRecorder.cxx
  95. 112 0
      panda/src/recorder/mouseRecorder.h
  96. 43 0
      panda/src/recorder/recorderBase.I
  97. 89 0
      panda/src/recorder/recorderBase.cxx
  98. 102 0
      panda/src/recorder/recorderBase.h
  99. 294 0
      panda/src/recorder/recorderController.I
  100. 370 0
      panda/src/recorder/recorderController.cxx

+ 2 - 0
direct/src/distributed/ClientRepository.py

@@ -17,6 +17,8 @@ class ClientRepository(ConnectionRepository.ConnectionRepository):
 
     def __init__(self, dcFileName):
         ConnectionRepository.ConnectionRepository.__init__(self, base.config)
+
+        self.recorder = base.recorder
         
         self.number2cdc={}
         self.name2cdc={}

+ 35 - 0
direct/src/distributed/ConnectionRepository.py

@@ -44,6 +44,7 @@ class ConnectionRepository(DirectObject.DirectObject):
         self.cw = None
 
         self.tcpConn = None
+        self.recorder = None
 
         # Reader statistics
         self.rsDatagramCount = 0
@@ -65,6 +66,22 @@ class ConnectionRepository(DirectObject.DirectObject):
         known.
         """
 
+        if self.recorder and self.recorder.isPlaying():
+
+            # If we have a recorder and it's already in playback mode,
+            # don't actually attempt to connect to a gameserver since
+            # we don't need to.  Just let it play back the data.
+            self.notify.info("Not connecting to gameserver; using playback data instead.")
+
+            self.connectHttp = 1
+            self.tcpConn = SocketStreamRecorder()
+            self.recorder.addRecorder('gameserver', self.tcpConn)
+            
+            self.startReaderPollTask()
+            if successCallback:
+                successCallback(*successArgs)
+            return
+
         hasProxy = 0
         if self.checkHttp():
             proxies = self.http.getProxiesForUrl(serverList[0])
@@ -151,6 +168,24 @@ class ConnectionRepository(DirectObject.DirectObject):
         if ch.isConnectionReady():
             self.tcpConn = ch.getConnection()
             self.tcpConn.userManagesMemory = 1
+
+            if self.recorder:
+                # If we have a recorder, we wrap the connect inside a
+                # SocketStreamRecorder, which will trap incoming data
+                # when the recorder is set to record mode.  (It will
+                # also play back data when the recorder is in playback
+                # mode, but in that case we never get this far in the
+                # code, since we just create an empty
+                # SocketStreamRecorder without actually connecting to
+                # the gameserver.)
+                stream = SocketStreamRecorder(self.tcpConn, 1)
+                self.recorder.addRecorder('gameserver', stream)
+
+                # In this case, we pass ownership of the original
+                # connection to the SocketStreamRecorder object.
+                self.tcpConn.userManagesMemory = 0
+                self.tcpConn = stream
+            
             self.startReaderPollTask()
             if successCallback:
                 successCallback(*successArgs)

+ 19 - 11
direct/src/showbase/EventManager.py

@@ -1,5 +1,4 @@
 
-from libpandaexpressModules import *
 from MessengerGlobal import *
 from TaskManagerGlobal import *
 from DirectNotifyGlobal import *
@@ -16,15 +15,8 @@ class EventManager:
         if (EventManager.notify == None):
             EventManager.notify = directNotify.newCategory("EventManager")
 
-        if eventQueue != None:
-            self.eventQueue = eventQueue
-        else:
-            self.eventQueue = EventQueue.getGlobalEventQueue()
-        
-        try:
-            self.eventHandler = EventHandler.getGlobalEventHandler(self.eventQueue)
-        except:
-            self.eventHandler = EventHandler(self.eventQueue)
+        self.eventQueue = eventQueue
+        self.eventHandler = None
 
     def doEvents(self):
         """
@@ -78,13 +70,29 @@ class EventManager:
             else:
                 messenger.send(eventName)
             # Also send the event down into C++ land
-            self.eventHandler.dispatchEvent(event)
+            if self.eventHandler:
+                self.eventHandler.dispatchEvent(event)
+            
         else:
             # An unnamed event from C++ is probably a bad thing
             EventManager.notify.warning('unnamed event in processEvent')
 
 
     def restart(self):
+        from PandaModules import EventQueue, EventHandler
+        
+        if self.eventQueue == None:
+            self.eventQueue = EventQueue.getGlobalEventQueue()
+
+        if self.eventHandler == None:
+            if self.eventQueue == EventQueue.getGlobalEventQueue():
+                # If we are using the global event queue, then we also
+                # want to use the global event handler.
+                self.eventHandler = EventHandler.getGlobalEventHandler(self.eventQueue)
+            else:
+                # Otherwise, we need our own event handler.
+                self.eventHandler = EventHandler(self.eventQueue)
+
         taskMgr.add(self.eventLoopTask, 'eventManager')
 
     def shutdown(self):

+ 38 - 0
direct/src/showbase/ShowBase.py

@@ -132,6 +132,28 @@ class ShowBase(DirectObject.DirectObject):
         self.cameraList = []
         self.camera2d = self.render2d.attachNewNode('camera2d')
 
+        # Maybe create a RecorderController to record and/or play back
+        # the user session.
+        self.recorder = None
+        playbackSession = self.config.GetString('playback-session', '')
+        recordSession = self.config.GetString('record-session', '')
+        if playbackSession:
+            self.recorder = RecorderController()
+            self.recorder.beginPlayback(Filename.fromOsSpecific(playbackSession))
+        elif recordSession:
+            self.recorder = RecorderController()
+            self.recorder.beginRecord(Filename.fromOsSpecific(recordSession))
+
+        if self.recorder:
+            # If we're either playing back or recording, pass the
+            # random seed into the system so each session will have
+            # the same random seed.
+            import random, whrandom
+
+            seed = self.recorder.getRandomSeed()
+            random.seed(seed)
+            whrandom.seed(seed & 0xff, (seed >> 8) & 0xff, (seed >> 16) & 0xff)
+
         # Now that we've set up the window structures, assign an exitfunc.
         self.oldexitfunc = getattr(sys, 'exitfunc', None)
         sys.exitfunc = self.exitfunc
@@ -563,6 +585,16 @@ class ShowBase(DirectObject.DirectObject):
         self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(win, 0, 'mak'))
         self.mouseWatcherNode = MouseWatcher('mouseWatcher')
         self.mouseWatcher = self.mak.attachNewNode(self.mouseWatcherNode)
+
+        if self.recorder:
+            # If we have a recorder, the mouseWatcher belongs under a
+            # special MouseRecorder node, which may intercept the
+            # mouse activity.
+            mouseRecorder = MouseRecorder('mouse')
+            self.recorder.addRecorder('mouse', mouseRecorder.upcastToRecorderBase())
+            np = self.mak.attachNewNode(mouseRecorder)
+            self.mouseWatcher.reparentTo(np)
+        
         mb = self.mouseWatcherNode.getModifierButtons()
         mb.addButton(KeyboardButton.shift())
         mb.addButton(KeyboardButton.control())
@@ -867,6 +899,9 @@ class ShowBase(DirectObject.DirectObject):
             # as we reasonably can before the renderFrame().
             onScreenDebug.render()
 
+        if self.recorder:
+            self.recorder.recordFrame()
+
         # Finally, render the frame.
         self.graphicsEngine.renderFrame()
         if self.clusterSyncFlag:
@@ -876,6 +911,9 @@ class ShowBase(DirectObject.DirectObject):
             # We clear the text buffer for the onScreenDebug as soon
             # as we reasonably can after the renderFrame().
             onScreenDebug.clear()
+
+        if self.recorder:
+            self.recorder.playFrame()
     
         if self.mainWinMinimized:
             # If the main window is minimized, slow down the app a bit

+ 3 - 3
panda/metalibs/panda/Sources.pp

@@ -10,15 +10,15 @@
 #define WIN_SYS_LIBS $[WIN_SYS_LIBS] Ws2_32.lib
 
 #define COMPONENT_LIBS \
-    pgraph \
+    recorder pgraph \
     pvrpn grutil chan chancfg pstatclient \
     char chat collide cull device \
-    dgraph display gobj graph gsgbase \
+    dgraph display event gobj graph gsgbase \
     gsgmisc light linmath mathutil net \
     parametrics pnm \
     pnmimagetypes pnmimage sgattrib sgmanip sgraph sgraphutil \
     switchnode pnmtext text tform tiff lerp loader putil \
-    audio pgui pandabase 
+    audio pgui pandabase
 
 
 

+ 1 - 1
panda/metalibs/pandaexpress/Sources.pp

@@ -8,7 +8,7 @@
 #define BUILDING_DLL BUILDING_PANDAEXPRESS
 #define USE_PACKAGES net
 
-#define COMPONENT_LIBS downloader event ipc express pandabase
+#define COMPONENT_LIBS downloader ipc express pandabase
 #define OTHER_LIBS dtoolconfig dtool
 
 #begin metalib_target

+ 4 - 4
panda/src/collide/collisionEntry.h

@@ -27,7 +27,7 @@
 #include "collisionRecorder.h"
 
 #include "transformState.h"
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 #include "luse.h"
 #include "pointerTo.h"
 #include "pandaNode.h"
@@ -47,7 +47,7 @@
 //               is up to the handler to determine what information is
 //               known and to do the right thing with it.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA CollisionEntry : public TypedReferenceCount {
+class EXPCL_PANDA CollisionEntry : public TypedWritableReferenceCount {
 public:
   INLINE CollisionEntry();
   CollisionEntry(const CollisionEntry &copy);
@@ -150,9 +150,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "CollisionEntry",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 3 - 3
panda/src/device/trackerNode.cxx

@@ -30,9 +30,9 @@ TrackerNode::
 TrackerNode(ClientBase *client, const string &device_name) :
   DataNode(device_name)
 {
-  _transform_output = define_output("transform", EventStoreTransform::get_class_type());
+  _transform_output = define_output("transform", TransformState::get_class_type());
 
-  _transform = new EventStoreTransform(TransformState::make_identity());
+  _transform = TransformState::make_identity();
 
   nassertv(client != (ClientBase *)NULL);
   set_tracker_coordinate_system(client->get_coordinate_system());
@@ -99,7 +99,7 @@ do_transmit_data(const DataNodeTransmit &, DataNodeTransmit &output) {
 
     // Now send our matrix down the pipe.  TODO: store this
     // componentwise instead of just as a matrix-based transform.
-    _transform->set_value(TransformState::make_mat(_mat));
+    _transform = TransformState::make_mat(_mat);
     output.set_data(_transform_output, EventParameter(_transform));
   }
 }

+ 1 - 1
panda/src/device/trackerNode.h

@@ -63,7 +63,7 @@ private:
   // outputs
   int _transform_output;
 
-  PT(EventStoreTransform) _transform;
+  CPT(TransformState) _transform;
 
 private:
   PT(ClientTrackerDevice) _tracker;

+ 5 - 1
panda/src/dgraph/config_dgraph.cxx

@@ -18,12 +18,16 @@
 
 #include "config_dgraph.h"
 #include "dataNode.h"
+#include "dataNodeTransmit.h"
 
-#include <dconfig.h>
+#include "dconfig.h"
 
 Configure(config_dgraph);
 NotifyCategoryDef(dgraph, "");
 
 ConfigureFn(config_dgraph) {
   DataNode::init_type();
+  DataNodeTransmit::init_type();
+
+  DataNodeTransmit::register_with_read_factory();
 }

+ 23 - 0
panda/src/dgraph/dataNode.cxx

@@ -333,3 +333,26 @@ reconnect() {
       << "No data connected to " << *this << "\n";
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNode::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void DataNode::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  PandaNode::write_datagram(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNode::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 Lens.
+////////////////////////////////////////////////////////////////////
+void DataNode::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  PandaNode::fillin(scan, manager);
+}

+ 6 - 0
panda/src/dgraph/dataNode.h

@@ -119,6 +119,12 @@ private:
   typedef pvector<DataConnection> DataConnections;
   DataConnections _data_connections;
 
+public:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 0 - 9
panda/src/dgraph/dataNodeTransmit.I

@@ -47,15 +47,6 @@ operator = (const DataNodeTransmit &copy) {
   _data = copy._data;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DataNodeTransmit::Destructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE DataNodeTransmit::
-~DataNodeTransmit() {
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DataNodeTransmit::reserve
 //       Access: Public

+ 101 - 0
panda/src/dgraph/dataNodeTransmit.cxx

@@ -17,6 +17,19 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "dataNodeTransmit.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+
+TypeHandle DataNodeTransmit::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+DataNodeTransmit::
+~DataNodeTransmit() {
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DataNodeTransmit::slot_data
@@ -31,3 +44,91 @@ slot_data(int index) {
     _data.push_back(EventParameter());
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void DataNodeTransmit::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void DataNodeTransmit::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  TypedWritable::write_datagram(manager, dg);
+
+  dg.add_uint16(_data.size());
+  Data::const_iterator di;
+  for (di = _data.begin(); di != _data.end(); ++di) {
+    const EventParameter &param = (*di);
+    TypedWritableReferenceCount *ptr = param.get_ptr();
+    manager->write_pointer(dg, ptr);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::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 DataNodeTransmit::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = TypedWritable::complete_pointers(p_list, manager);
+
+  Data::iterator di;
+  for (di = _data.begin(); di != _data.end(); ++di) {
+    (*di) = EventParameter(DCAST(TypedWritableReferenceCount, p_list[pi++]));
+  }
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *DataNodeTransmit::
+make_from_bam(const FactoryParams &params) {
+  DataNodeTransmit *xmit = new DataNodeTransmit;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  xmit->fillin(scan, manager);
+
+  return xmit;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DataNodeTransmit::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 Lens.
+////////////////////////////////////////////////////////////////////
+void DataNodeTransmit::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  TypedWritable::fillin(scan, manager);
+
+  int num_params = scan.get_uint16();
+  _data.reserve(num_params);
+  for (int i = 0; i < num_params; i++) {
+    manager->read_pointer(scan);
+    _data.push_back(EventParameter());
+  }
+}

+ 35 - 2
panda/src/dgraph/dataNodeTransmit.h

@@ -21,6 +21,12 @@
 
 #include "pandabase.h"
 #include "eventParameter.h"
+#include "typedWritable.h"
+
+class Datagram;
+class DatagramIterator;
+class BamReader;
+class BamWriter;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DataNodeTransmit
@@ -29,12 +35,12 @@
 //               array of EventParameters, one for each registered
 //               input or output wire.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA DataNodeTransmit {
+class EXPCL_PANDA DataNodeTransmit : public TypedWritable {
 public:
   INLINE DataNodeTransmit();
   INLINE DataNodeTransmit(const DataNodeTransmit &copy);
   INLINE void operator = (const DataNodeTransmit &copy);
-  INLINE ~DataNodeTransmit();
+  virtual ~DataNodeTransmit();
 
   INLINE void reserve(int num_wires);
 
@@ -47,6 +53,33 @@ private:
 
   typedef pvector<EventParameter> Data;
   Data _data;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  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() {
+    TypedWritable::init_type();
+    register_type(_type_handle, "DataNodeTransmit",
+                  TypedWritable::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 "dataNodeTransmit.I"

+ 10 - 4
panda/src/display/graphicsWindow.h

@@ -29,7 +29,7 @@
 #include "graphicsStateGuardian.h"
 #include "clearableRegion.h"
 
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 #include "mouseData.h"
 #include "modifierButtons.h"
 #include "buttonEvent.h"
@@ -55,8 +55,14 @@
 //               input.  The actual rendering, and anything associated
 //               with the graphics context itself, is managed by the
 //               window's GraphicsStateGuardian.
+//
+//               GraphicsWindows are not actually writable to bam
+//               files, of course, but they may be passed as event
+//               parameters, so they inherit from
+//               TypedWritableReferenceCount instead of
+//               TypedReferenceCount for that convenience.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA GraphicsWindow : public TypedReferenceCount, public ClearableRegion {
+class EXPCL_PANDA GraphicsWindow : public TypedWritableReferenceCount, public ClearableRegion {
 protected:
   GraphicsWindow(GraphicsPipe *pipe, GraphicsStateGuardian *gsg);
 
@@ -183,9 +189,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "GraphicsWindow",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 1 - 1
panda/src/downloader/Sources.pp

@@ -1,4 +1,4 @@
-#define LOCAL_LIBS event express pandabase
+#define LOCAL_LIBS express pandabase
 #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
                    dtoolutil:c dtoolbase:c dtool:m
 #define USE_PACKAGES zlib net ssl

+ 20 - 12
panda/src/event/Sources.pp

@@ -1,4 +1,4 @@
-#define LOCAL_LIBS express pandabase
+#define LOCAL_LIBS putil express pandabase
 #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
                    dtoolutil:c dtoolbase:c dtool:m
 
@@ -8,20 +8,28 @@
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx
   
   #define SOURCES \
-     config_event.h event.I event.h eventHandler.h eventHandler.I eventParameter.I \
-     eventParameter.h eventQueue.I eventQueue.h eventReceiver.h \
-     pt_Event.h throw_event.I throw_event.h 
+    config_event.h \
+    buttonEvent.I buttonEvent.h \
+    buttonEventList.I buttonEventList.h \
+    event.I event.h eventHandler.h eventHandler.I \
+    eventParameter.I eventParameter.h \
+    eventQueue.I eventQueue.h eventReceiver.h \
+    pt_Event.h throw_event.I throw_event.h 
     
   #define INCLUDED_SOURCES \
-     config_event.cxx event.cxx eventHandler.cxx \ 
-     eventParameter.cxx eventQueue.cxx eventReceiver.cxx \
-     pt_Event.cxx
+    buttonEvent.cxx \
+    buttonEventList.cxx \
+    config_event.cxx event.cxx eventHandler.cxx \ 
+    eventParameter.cxx eventQueue.cxx eventReceiver.cxx \
+    pt_Event.cxx
 
-  #define INSTALL_HEADERS                                       \
-    event.I event.h eventHandler.h eventHandler.I eventParameter.I eventParameter.h    \
-    eventQueue.h eventQueue.I throw_event.h throw_event.I       \
-    eventReceiver.h                                             \
-    pt_Event.h
+  #define INSTALL_HEADERS \
+    buttonEvent.I buttonEvent.h \
+    buttonEventList.I buttonEventList.h \
+    event.I event.h eventHandler.h eventHandler.I \
+    eventParameter.I eventParameter.h \
+    eventQueue.I eventQueue.h eventReceiver.h \
+    pt_Event.h throw_event.I throw_event.h 
 
   #define IGATESCAN all
 

+ 20 - 0
panda/src/putil/buttonEvent.I → panda/src/event/buttonEvent.I

@@ -125,3 +125,23 @@ operator < (const ButtonEvent &other) const {
 
   return _type < other._type;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEvent::update_mods
+//       Access: Published
+//  Description: Calls button_down() or button_up(), as appropriate,
+//               according to the ButtonEvent.
+////////////////////////////////////////////////////////////////////
+INLINE bool ButtonEvent::
+update_mods(ModifierButtons &mods) const {
+  switch (_type) {
+  case T_down:
+    return mods.button_down(_button);
+
+  case T_up:
+    return mods.button_up(_button);
+
+  default:
+    return false;
+  }
+}

+ 94 - 0
panda/src/event/buttonEvent.cxx

@@ -0,0 +1,94 @@
+// Filename: buttonEvent.cxx
+// Created by:  drose (01Mar00)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "buttonEvent.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "buttonRegistry.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEvent::output
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void ButtonEvent::
+output(ostream &out) const {
+  switch (_type) {
+  case T_down:
+    out << "button " << _button << " down";
+    break;
+
+  case T_resume_down:
+    out << "button " << _button << " resume down";
+    break;
+
+  case T_up:
+    out << "button " << _button << " up";
+    break;
+
+  case T_keystroke:
+    out << "keystroke " << _keycode;
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEvent::write_datagram
+//       Access: Public
+//  Description: Writes the event into a datagram.
+////////////////////////////////////////////////////////////////////
+void ButtonEvent::
+write_datagram(Datagram &dg) const {
+  dg.add_uint8(_type);
+  switch (_type) {
+  case T_down:
+  case T_resume_down:
+  case T_up:
+    // We write the button name.  This is not particularly compact, but
+    // presumably we don't get thousands of button events per frame, and
+    // it is robust as the button index may change between sessions but
+    // the button name will not.
+    dg.add_string(_button.get_name());
+    break;
+
+  case T_keystroke:
+    dg.add_int16(_keycode);
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEvent::read_datagram
+//       Access: Public
+//  Description: Restores the event from the datagram.
+////////////////////////////////////////////////////////////////////
+void ButtonEvent::
+read_datagram(DatagramIterator &scan) {
+  _type = (Type)scan.get_uint8();
+  switch (_type) {
+  case T_down:
+  case T_resume_down:
+  case T_up:
+    _button = ButtonRegistry::ptr()->get_button(scan.get_string());
+    break;
+
+  case T_keystroke:
+    _keycode = scan.get_int16();
+    break;
+  }
+}

+ 10 - 0
panda/src/putil/buttonEvent.h → panda/src/event/buttonEvent.h

@@ -23,6 +23,10 @@
 
 #include "buttonHandle.h"
 #include "clockObject.h"
+#include "modifierButtons.h"
+
+class Datagram;
+class DatagramIterator;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ButtonEvent
@@ -81,8 +85,14 @@ public:
   INLINE bool operator != (const ButtonEvent &other) const;
   INLINE bool operator < (const ButtonEvent &other) const;
 
+  INLINE bool update_mods(ModifierButtons &mods) const;
+
   void output(ostream &out) const;
 
+  void write_datagram(Datagram &dg) const;
+  void read_datagram(DatagramIterator &scan);
+
+public:
   // _button will be filled in if type is T_down, T_resume_down, or
   // T_up.
   ButtonHandle _button;

+ 0 - 0
panda/src/putil/buttonEventList.I → panda/src/event/buttonEventList.I


+ 163 - 0
panda/src/event/buttonEventList.cxx

@@ -0,0 +1,163 @@
+// Filename: buttonEventList.cxx
+// Created by:  drose (12Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "buttonEventList.h"
+#include "modifierButtons.h"
+#include "indent.h"
+
+TypeHandle ButtonEventList::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::add_events
+//       Access: Public
+//  Description: Appends the events from the other list onto the end
+//               of this one.
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+add_events(const ButtonEventList &other) {
+  _events.reserve(_events.size() + other._events.size());
+  Events::const_iterator ei;
+  for (ei = other._events.begin(); ei != other._events.end(); ++ei) {
+    _events.push_back(*ei);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::update_mods
+//       Access: Public
+//  Description: Updates the indicated ModifierButtons object with all
+//               of the button up/down transitions indicated in the
+//               list.
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+update_mods(ModifierButtons &mods) const {
+  Events::const_iterator ei;
+  for (ei = _events.begin(); ei != _events.end(); ++ei) {
+    (*ei).update_mods(mods);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+output(ostream &out) const {
+  if (_events.empty()) {
+    out << "(no buttons)";
+  } else {
+    Events::const_iterator ei;
+    ei = _events.begin();
+    out << "(" << (*ei);
+    ++ei;
+    while (ei != _events.end()) {
+      out << " " << (*ei);
+      ++ei;
+    }
+    out << ")";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level) << _events.size() << " events:\n";
+  Events::const_iterator ei;
+  for (ei = _events.begin(); ei != _events.end(); ++ei) {
+    indent(out, indent_level + 2) << (*ei) << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  TypedWritable::write_datagram(manager, dg);
+
+  dg.add_uint16(_events.size());
+  Events::const_iterator ei;
+  for (ei = _events.begin(); ei != _events.end(); ++ei) {
+    (*ei).write_datagram(dg);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *ButtonEventList::
+make_from_bam(const FactoryParams &params) {
+  ButtonEventList *list = new ButtonEventList;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  list->fillin(scan, manager);
+
+  return list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::fillin
+//       Access: Public
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new ButtonEventList.
+//
+//               This function is normally protected, but it is
+//               declared public in this case so that MouseRecorder
+//               may call it to read a ButtonEventList from the middle
+//               of a datagram.
+////////////////////////////////////////////////////////////////////
+void ButtonEventList::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  TypedWritable::fillin(scan, manager);
+
+  int num_events = scan.get_uint16();
+  _events.clear();
+  _events.reserve(num_events);
+  for (int i = 0; i < num_events; i++) {
+    ButtonEvent event;
+    event.read_datagram(scan);
+    _events.push_back(event);
+  }
+}

+ 13 - 0
panda/src/putil/buttonEventList.h → panda/src/event/buttonEventList.h

@@ -27,6 +27,8 @@
 #include "pvector.h"
 
 class ModifierButtons;
+class Datagram;
+class DatagramIterator;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ButtonEventList
@@ -45,6 +47,7 @@ public:
   INLINE const ButtonEvent &get_event(int n) const;
   INLINE void clear();
 
+  void add_events(const ButtonEventList &other);
   void update_mods(ModifierButtons &mods) const;
 
   virtual void output(ostream &out) const;
@@ -53,6 +56,16 @@ public:
 private:
   typedef pvector<ButtonEvent> Events;
   Events _events;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
+public:
+  void fillin(DatagramIterator &scan, BamReader *manager);
   
 public:
   static TypeHandle get_class_type() {

+ 8 - 1
panda/src/event/config_event.cxx

@@ -17,21 +17,28 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "config_event.h"
+#include "buttonEventList.h"
 #include "event.h"
 #include "eventHandler.h"
 #include "eventParameter.h"
 
-#include <dconfig.h>
+#include "dconfig.h"
 
 Configure(config_event);
 NotifyCategoryDef(event, "");
 
 ConfigureFn(config_event) {
+  ButtonEventList::init_type();
   Event::init_type();
   EventHandler::init_type();
   EventStoreValueBase::init_type();
   EventStoreInt::init_type("EventStoreInt");
   EventStoreDouble::init_type("EventStoreDouble");
   EventStoreString::init_type("EventStoreString");
+
+  ButtonEventList::register_with_read_factory();
+  EventStoreInt::register_with_read_factory();
+  EventStoreDouble::register_with_read_factory();
+  EventStoreString::register_with_read_factory();
 }
 

+ 1 - 1
panda/src/event/config_event.h

@@ -21,7 +21,7 @@
 
 #include "pandabase.h"
 
-#include <notifyCategoryProxy.h>
+#include "notifyCategoryProxy.h"
 
 NotifyCategoryDecl(event, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 

+ 1 - 1
panda/src/event/event.h

@@ -36,7 +36,7 @@ class EventReceiver;
 //               makes it too expensive to get its name the Python
 //               code.  Now it just copies the Namable interface in.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDAEXPRESS Event : public TypedReferenceCount {
+class EXPCL_PANDA Event : public TypedReferenceCount {
 PUBLISHED:
   Event(const string &event_name, EventReceiver *receiver = NULL);
   Event(const Event &copy);

+ 2 - 2
panda/src/event/eventHandler.I

@@ -1,5 +1,5 @@
-// Filename: eventHandler.h
-// Created by:  drose (08Feb99)
+// Filename: eventHandler.I
+// Created by:  skyler (27Jan04)
 //
 ////////////////////////////////////////////////////////////////////
 //

+ 65 - 5
panda/src/event/eventParameter.I

@@ -35,8 +35,8 @@ EventParameter() {
 //     Function: EventParameter::Pointer constructor
 //       Access: Public
 //  Description: Defines an EventParameter that stores a pointer to
-//               any kind of TypedReferenceCount object.  This is the
-//               most general constructor.
+//               any kind of TypedWritableReferenceCount object.  This
+//               is the most general constructor.
 //
 //               This accepts a const pointer, even though it stores
 //               (and eventually returns) a non-const pointer.  This
@@ -45,7 +45,7 @@ EventParameter() {
 //               constness.  Be careful.
 ////////////////////////////////////////////////////////////////////
 INLINE EventParameter::
-EventParameter(const TypedReferenceCount *ptr) : _ptr((TypedReferenceCount *)ptr) { }
+EventParameter(const TypedWritableReferenceCount *ptr) : _ptr((TypedWritableReferenceCount *)ptr) { }
 
 
 ////////////////////////////////////////////////////////////////////
@@ -105,7 +105,7 @@ operator = (const EventParameter &other) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool EventParameter::
 is_empty() const {
-  return (_ptr == (TypedReferenceCount *)NULL);
+  return (_ptr == (TypedWritableReferenceCount *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -201,7 +201,7 @@ get_string_value() const {
 //               contains.  This is the only way to retrieve the value
 //               when it is not one of the above predefined types.
 ////////////////////////////////////////////////////////////////////
-INLINE TypedReferenceCount *EventParameter::
+INLINE TypedWritableReferenceCount *EventParameter::
 get_ptr() const {
   return _ptr;
 }
@@ -248,3 +248,63 @@ void EventStoreValue<Type>::
 output(ostream &out) const {
   out << _value;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventStoreValue::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+void EventStoreValue<Type>::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventStoreValue::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+void EventStoreValue<Type>::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  TypedWritable::write_datagram(manager, dg);
+  generic_write_datagram(dg, _value);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventStoreValue::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+TypedWritable *EventStoreValue<Type>::
+make_from_bam(const FactoryParams &params) {
+  EventStoreValue<Type> *esv = new EventStoreValue<Type>;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  esv->fillin(scan, manager);
+
+  return esv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EventStoreValue::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 Lens.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+void EventStoreValue<Type>::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  TypedWritable::fillin(scan, manager);
+  generic_read_datagram(_value, scan);
+}

+ 1 - 1
panda/src/event/eventParameter.cxx

@@ -33,7 +33,7 @@ TypeHandle EventStoreValueBase::_type_handle;
 ////////////////////////////////////////////////////////////////////
 void EventParameter::
 output(ostream &out) const {
-  if (_ptr == (TypedReferenceCount *)NULL) {
+  if (_ptr == (TypedWritableReferenceCount *)NULL) {
     out << "(empty)";
 
   } else if (_ptr->is_of_type(EventStoreValueBase::get_class_type())) {

+ 33 - 19
panda/src/event/eventParameter.h

@@ -23,23 +23,26 @@
 
 #include "typedef.h"
 #include "typedObject.h"
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 #include "pointerTo.h"
+#include "bamReader.h"
+#include "bamWriter.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : EventParameter
 // Description : An optional parameter associated with an event.  Each
 //               event may have zero or more of these.  Each parameter
-//               stores a pointer to a TypedReferenceCount object,
-//               which of course could be pretty much anything.  To
-//               store a simple value like a double or a string, the
-//               EventParameter constructors transparently use the
-//               EventStoreValue template class, defined below.
+//               stores a pointer to a TypedWritableReferenceCount
+//               object, which of course could be pretty much
+//               anything.  To store a simple value like a double or a
+//               string, the EventParameter constructors transparently
+//               use the EventStoreValue template class, defined
+//               below.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDAEXPRESS EventParameter {
+class EXPCL_PANDA EventParameter {
 PUBLISHED:
   INLINE EventParameter();
-  INLINE EventParameter(const TypedReferenceCount *ptr);
+  INLINE EventParameter(const TypedWritableReferenceCount *ptr);
   INLINE EventParameter(int value);
   INLINE EventParameter(double value);
   INLINE EventParameter(const string &value);
@@ -60,12 +63,12 @@ PUBLISHED:
   INLINE bool is_string() const;
   INLINE string get_string_value() const;
 
-  INLINE TypedReferenceCount *get_ptr() const;
+  INLINE TypedWritableReferenceCount *get_ptr() const;
 
   void output(ostream &out) const;
 
 private:
-  PT(TypedReferenceCount) _ptr;
+  PT(TypedWritableReferenceCount) _ptr;
 };
 
 INLINE ostream &operator << (ostream &out, const EventParameter &param);
@@ -76,7 +79,7 @@ INLINE ostream &operator << (ostream &out, const EventParameter &param);
 //               which serves mainly to define the placeholder for the
 //               virtual output function.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDAEXPRESS EventStoreValueBase : public TypedReferenceCount {
+class EXPCL_PANDAEXPRESS EventStoreValueBase : public TypedWritableReferenceCount {
 public:
   virtual void output(ostream &out) const=0;
 
@@ -89,9 +92,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "EventStoreValueBase",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
 
 private:
@@ -103,12 +106,15 @@ private:
 // Description : A handy class object for storing simple values (like
 //               integers or strings) passed along with an Event.
 //               This is essentially just a wrapper around whatever
-//               data type you like, to make it a TypedReferenceCount
-//               object which can be passed along inside an
-//               EventParameter.
+//               data type you like, to make it a
+//               TypedWritableReferenceCount object which can be
+//               passed along inside an EventParameter.
 ////////////////////////////////////////////////////////////////////
 template<class Type>
 class EventStoreValue : public EventStoreValueBase {
+private:
+  // This constructor is only for make_from_bam().
+  EventStoreValue() { }
 public:
   EventStoreValue(const Type &value) : _value(value) { }
 
@@ -119,6 +125,14 @@ public:
 
   Type _value;
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;
@@ -141,9 +155,9 @@ private:
   static TypeHandle _type_handle;
 };
 
-EXPORT_TEMPLATE_CLASS(EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS, EventStoreValue<int>);
-EXPORT_TEMPLATE_CLASS(EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS, EventStoreValue<double>);
-EXPORT_TEMPLATE_CLASS(EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS, EventStoreValue<std::string>);
+EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, EventStoreValue<int>);
+EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, EventStoreValue<double>);
+EXPORT_TEMPLATE_CLASS(EXPCL_PANDA, EXPTP_PANDA, EventStoreValue<std::string>);
 
 typedef EventStoreValue<int> EventStoreInt;
 typedef EventStoreValue<double> EventStoreDouble;

+ 2 - 1
panda/src/event/event_composite1.cxx

@@ -1,4 +1,5 @@
-
+#include "buttonEvent.cxx"
+#include "buttonEventList.cxx"
 #include "config_event.cxx"
 #include "event.cxx"
 #include "eventHandler.cxx"

+ 25 - 0
panda/src/express/datagram.I

@@ -456,3 +456,28 @@ operator < (const Datagram &other) const {
   // One of the pointers is NULL, but not the other one.
   return _data.size() < other._data.size();
 }
+
+INLINE void
+generic_write_datagram(Datagram &dest, bool value) {
+  dest.add_bool(value);
+}
+
+INLINE void
+generic_write_datagram(Datagram &dest, int value) {
+  dest.add_int32(value);
+}
+
+INLINE void
+generic_write_datagram(Datagram &dest, float value) {
+  dest.add_float32(value);
+}
+
+INLINE void
+generic_write_datagram(Datagram &dest, double value) {
+  dest.add_float64(value);
+}
+
+INLINE void
+generic_write_datagram(Datagram &dest, const string &value) {
+  dest.add_string(value);
+}

+ 17 - 0
panda/src/express/datagram.h

@@ -120,6 +120,23 @@ private:
   static TypeHandle _type_handle;
 };
 
+// These generic functions are primarily for writing a value to a
+// datagram from within a template in which the actual type of the
+// value is not known.  If you do know the type, it's preferable to
+// use the explicit add_*() method from above instead.
+
+INLINE void
+generic_write_datagram(Datagram &dest, bool value);
+INLINE void
+generic_write_datagram(Datagram &dest, int value);
+INLINE void
+generic_write_datagram(Datagram &dest, float value);
+INLINE void
+generic_write_datagram(Datagram &dest, double value);
+INLINE void
+generic_write_datagram(Datagram &dest, const string &value);
+
+
 #include "datagram.I"
 
 #endif

+ 27 - 0
panda/src/express/datagramIterator.I

@@ -482,3 +482,30 @@ INLINE size_t DatagramIterator::
 get_current_index() const {
   return _current_index;
 }
+
+INLINE void
+generic_read_datagram(bool &result, DatagramIterator &source) {
+  result = source.get_bool();
+}
+
+INLINE void
+generic_read_datagram(int &result, DatagramIterator &source) {
+  result = source.get_int32();
+}
+
+INLINE void
+generic_read_datagram(float &result, DatagramIterator &source) {
+  result = source.get_float32();
+}
+
+INLINE void
+generic_read_datagram(double &result, DatagramIterator &source) {
+  result = source.get_float64();
+}
+
+INLINE void
+generic_read_datagram(string &result, DatagramIterator &source) {
+  result = source.get_string();
+}
+
+

+ 16 - 0
panda/src/express/datagramIterator.h

@@ -80,6 +80,22 @@ private:
   size_t _current_index;
 };
 
+// These generic functions are primarily for reading a value from a
+// datagram from within a template in which the actual type of the
+// value is not known.  If you do know the type, it's preferable to
+// use the explicit get_*() method from above instead.
+
+INLINE void
+generic_read_datagram(bool &result, DatagramIterator &source);
+INLINE void
+generic_read_datagram(int &result, DatagramIterator &source);
+INLINE void
+generic_read_datagram(float &result, DatagramIterator &source);
+INLINE void
+generic_read_datagram(double &result, DatagramIterator &source);
+INLINE void
+generic_read_datagram(string &result, DatagramIterator &source);
+
 #include "datagramIterator.I"
 
 #endif

+ 13 - 5
panda/src/express/dcast.cxx

@@ -35,16 +35,24 @@ bool
 _dcast_verify(TypeHandle want_handle, size_t want_size, 
               const TypedObject *ptr) {
   if (get_verify_dcast()) {
-    if ((ptr == (const TypedObject *)NULL)
+    if (ptr == (const TypedObject *)NULL) {
+      // This is allowed these days.  It used to be an error, but
+      // what the heck.
+      if (express_cat->is_debug()) {
+        express_cat->debug()
+          << "Attempt to cast NULL pointer to " 
+          << want_handle << "\n";
+      }
+      return true;
+    }
 #if defined(_DEBUG) && defined(_WIN32)
-        || IsBadWritePtr((TypedObject *)ptr, want_size)
-#endif
-        ) {
+    if (IsBadWritePtr((TypedObject *)ptr, want_size)) {
       express_cat->warning()
-        << "Attempt to cast NULL or invalid pointer to " 
+        << "Attempt to cast invalid pointer to " 
         << want_handle << "\n";
       return false;
     }
+#endif
     if (!ptr->is_of_type(want_handle)) {
       express_cat->error()
         << "Attempt to cast pointer from " << ptr->get_type()

+ 1 - 1
panda/src/express/typedReferenceCount.h

@@ -34,7 +34,7 @@
 //               pass around pointers to things which are both
 //               TypedObjects and ReferenceCounters.
 //               
-//               See Also TypeObject for detailed instructions.
+//               See also TypedObject for detailed instructions.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDAEXPRESS TypedReferenceCount : public TypedObject, public ReferenceCount {
 public:

+ 1 - 1
panda/src/framework/Sources.pp

@@ -5,7 +5,7 @@
   #define TARGET framework
   #define BUILDING_DLL BUILDING_FRAMEWORK
   #define LOCAL_LIBS \
-    pgui pgraph putil collide chan text chancfg \
+    recorder pgui pgraph putil collide chan text chancfg \
     pnmimage pnmimagetypes event
 
   #define SOURCES \

+ 4 - 0
panda/src/framework/config_framework.cxx

@@ -41,3 +41,7 @@ const bool show_frame_rate_meter = config_framework.GetBool("show-frame-rate-met
 const float win_background_r = config_framework.GetFloat("win-background-r", 0.41);
 const float win_background_g = config_framework.GetFloat("win-background-g", 0.41);
 const float win_background_b = config_framework.GetFloat("win-background-b", 0.41);
+
+const string record_session = config_framework.GetString("record-session", "");
+const string playback_session = config_framework.GetString("playback-session", "");
+

+ 3 - 0
panda/src/framework/config_framework.h

@@ -38,4 +38,7 @@ extern const float win_background_r;
 extern const float win_background_g;
 extern const float win_background_b;
 
+extern const string record_session;
+extern const string playback_session;
+
 #endif

+ 35 - 0
panda/src/framework/pandaFramework.I

@@ -172,6 +172,41 @@ get_highlight() const {
   return _highlight;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaFramework::get_recorder
+//       Access: Public
+//  Description: Returns the RecorderController that has been
+//               associated with the PandaFramework, if any, or NULL
+//               if none has (the normal case).
+//
+//               If a RecorderController is associated, it will
+//               presumably be used for recording user input to a
+//               session file, or for playing back the user input from
+//               a previously-recorded session.
+////////////////////////////////////////////////////////////////////
+INLINE RecorderController *PandaFramework::
+get_recorder() const {
+  return _recorder;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaFramework::set_recorder
+//       Access: Public
+//  Description: Assigns a RecorderController with the PandaFramework.
+//               This should be called before any windows are opened.
+//               The subsequently opened windows will register their
+//               user inputs with the recorder.
+//
+//               If a RecorderController is associated, it will
+//               presumably be used for recording user input to a
+//               session file, or for playing back the user input from
+//               a previously-recorded session.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaFramework::
+set_recorder(RecorderController *recorder) {
+  _recorder = recorder;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaFramework::set_exit_flag
 //       Access: Public

+ 23 - 0
panda/src/framework/pandaFramework.cxx

@@ -52,6 +52,19 @@ PandaFramework() :
   _highlight_wireframe.set_color(1.0f, 0.0f, 0.0f, 1.0f, 1);
   _default_keys_enabled = false;
   _exit_flag = false;
+
+  if (!playback_session.empty()) {
+    // If the config file so indicates, create a recorder and start it
+    // playing.
+    _recorder = new RecorderController;
+    _recorder->begin_playback(Filename::from_os_specific(playback_session));
+    
+  } else if (!record_session.empty()) {
+    // If the config file so indicates, create a recorder and start it
+    // recording.
+    _recorder = new RecorderController;
+    _recorder->begin_record(Filename::from_os_specific(record_session));
+  } 
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -115,6 +128,8 @@ close_framework() {
   _lighting_enabled = false;
   _default_keys_enabled = false;
   _exit_flag = false;
+
+  _recorder = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -603,8 +618,16 @@ do_frame() {
   DataGraphTraverser dg_trav;
   dg_trav.traverse(_data_root.node());
   _event_handler.process_events();
+  if (_recorder != (RecorderController *)NULL) {
+    _recorder->record_frame();
+  }
+
   _engine.render_frame();
 
+  if (_recorder != (RecorderController *)NULL) {
+    _recorder->play_frame();
+  }
+
   return !_exit_flag;
 }
 

+ 6 - 0
panda/src/framework/pandaFramework.h

@@ -28,6 +28,7 @@
 #include "graphicsPipe.h"
 #include "graphicsEngine.h"
 #include "graphicsWindow.h"
+#include "recorderController.h"
 #include "pointerTo.h"
 
 #include "pvector.h"
@@ -99,6 +100,9 @@ public:
   INLINE bool has_highlight() const;
   INLINE const NodePath &get_highlight() const;
 
+  INLINE RecorderController *get_recorder() const;
+  INLINE void set_recorder(RecorderController *recorder);
+
   void enable_default_keys();
 
   virtual bool do_frame();
@@ -177,6 +181,8 @@ private:
   KeyDefinitions _key_definitions;
 
   NodePath _help_text;
+
+  PT(RecorderController) _recorder;
 };
 
 #include "pandaFramework.I"

+ 9 - 0
panda/src/framework/windowFramework.cxx

@@ -19,6 +19,7 @@
 #include "windowFramework.h"
 #include "pandaFramework.h"
 #include "mouseAndKeyboard.h"
+#include "mouseRecorder.h"
 #include "buttonThrower.h"
 #include "transform2sg.h"
 #include "dSearchPath.h"
@@ -292,6 +293,14 @@ get_mouse() {
     MouseAndKeyboard *mouse_node = 
       new MouseAndKeyboard(_window, 0, "mouse");
     _mouse = data_root.attach_new_node(mouse_node);
+
+    RecorderController *recorder = _panda_framework->get_recorder();
+    if (recorder != (RecorderController *)NULL) {
+      // If we're in recording or playback mode, associate a recorder.
+      MouseRecorder *mouse_recorder = new MouseRecorder("mouse");
+      _mouse = _mouse.attach_new_node(mouse_recorder);
+      recorder->add_recorder("mouse", mouse_recorder);
+    }
   }
   return _mouse;
 }

+ 4 - 4
panda/src/framework/windowFramework.h

@@ -29,7 +29,7 @@
 #include "frameRateMeter.h"
 #include "pointerTo.h"
 #include "pvector.h"
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 
 class PandaFramework;
 class AmbientLight;
@@ -42,7 +42,7 @@ class GraphicsPipe;
 // Description : This encapsulates the data that is normally
 //               associated with a single window that we've opened.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_FRAMEWORK WindowFramework : public TypedReferenceCount {
+class EXPCL_FRAMEWORK WindowFramework : public TypedWritableReferenceCount {
 protected:
   WindowFramework(PandaFramework *panda_framework);
 public:
@@ -146,9 +146,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "WindowFramework",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 3 - 0
panda/src/gobj/config_gobj.cxx

@@ -208,5 +208,8 @@ ConfigureFn(config_gobj) {
   GeomTrifan::register_with_read_factory();
   GeomSphere::register_with_read_factory();
   Material::register_with_read_factory();
+  OrthographicLens::register_with_read_factory();
+  MatrixLens::register_with_read_factory();
+  PerspectiveLens::register_with_read_factory();
   Texture::register_with_read_factory();
 }

+ 47 - 0
panda/src/gobj/lens.cxx

@@ -1734,3 +1734,50 @@ sqr_dist_to_line(const LPoint3f &point, const LPoint3f &origin,
   float leg = d.dot(norm);
   return hyp_2 - leg * leg;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void Lens::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  TypedWritable::write_datagram(manager, dg);
+
+  dg.add_string(_change_event);
+  dg.add_uint8((int)_cs);
+  _film_size.write_datagram(dg);
+  _film_offset.write_datagram(dg);
+  dg.add_float32(_focal_length);
+  _fov.write_datagram(dg);
+  dg.add_float32(_aspect_ratio);
+  dg.add_float32(_near_distance);
+  dg.add_float32(_far_distance);
+  dg.add_uint16(_user_flags);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lens::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 Lens.
+////////////////////////////////////////////////////////////////////
+void Lens::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  TypedWritable::fillin(scan, manager);
+
+  _change_event = scan.get_string();
+  _cs = (CoordinateSystem)scan.get_uint8();
+  _film_size.read_datagram(scan);
+  _film_offset.read_datagram(scan);
+  _focal_length = scan.get_float32();
+  _fov.read_datagram(scan);
+  _aspect_ratio = scan.get_float32();
+  _near_distance = scan.get_float32();
+  _far_distance = scan.get_float32();
+  _user_flags = scan.get_uint16();
+
+  _comp_flags = 0;
+}

+ 10 - 4
panda/src/gobj/lens.h

@@ -21,7 +21,7 @@
 
 #include "pandabase.h"
 
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 #include "luse.h"
 #include "geom.h"
 #include "updateSeq.h"
@@ -40,7 +40,7 @@ class BoundingVolume;
 //               also used in other contexts, however; for instance, a
 //               Spotlight is also defined using a lens.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA Lens : public TypedReferenceCount {
+class EXPCL_PANDA Lens : public TypedWritableReferenceCount {
 public:
   Lens();
   Lens(const Lens &copy);
@@ -244,6 +244,12 @@ protected:
 
   static const float _default_fov;
 
+public:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();
@@ -253,9 +259,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "Lens",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
 
 private:

+ 31 - 0
panda/src/gobj/matrixLens.cxx

@@ -68,3 +68,34 @@ compute_projection_mat() {
   adjust_comp_flags(CF_projection_mat_inv, 
                     CF_projection_mat);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: MatrixLens::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void MatrixLens::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MatrixLens::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *MatrixLens::
+make_from_bam(const FactoryParams &params) {
+  MatrixLens *lens = new MatrixLens;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  lens->fillin(scan, manager);
+
+  return lens;
+}

+ 10 - 4
panda/src/gobj/matrixLens.h

@@ -27,10 +27,10 @@
 ////////////////////////////////////////////////////////////////////
 //       Class : MatrixLens
 // Description : A completely generic linear lens.  This is provided
-//               for the benefit low-level code that wants to specify
-//               a perspective or orthographic frustum via an explicit
-//               projection matrix, but not mess around with fov's or
-//               focal lengths or any of that nonsense.
+//               for the benefit of low-level code that wants to
+//               specify a perspective or orthographic frustum via an
+//               explicit projection matrix, but not mess around with
+//               fov's or focal lengths or any of that nonsense.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA MatrixLens : public Lens {
 PUBLISHED:
@@ -56,6 +56,12 @@ protected:
 private:
   LMatrix4f _user_mat;
 
+public:
+  static void register_with_read_factory();
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 31 - 0
panda/src/gobj/orthographicLens.cxx

@@ -112,3 +112,34 @@ compute_projection_mat() {
   adjust_comp_flags(CF_projection_mat_inv, 
                     CF_projection_mat);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: OrthographicLens::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void OrthographicLens::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OrthographicLens::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *OrthographicLens::
+make_from_bam(const FactoryParams &params) {
+  OrthographicLens *lens = new OrthographicLens;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  lens->fillin(scan, manager);
+
+  return lens;
+}

+ 6 - 0
panda/src/gobj/orthographicLens.h

@@ -52,6 +52,12 @@ public:
 protected:
   virtual void compute_projection_mat();
 
+public:
+  static void register_with_read_factory();
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 31 - 0
panda/src/gobj/perspectiveLens.cxx

@@ -150,3 +150,34 @@ float PerspectiveLens::
 film_to_fov(float film_size, float focal_length, bool) const {
   return rad_2_deg(catan(film_size * 0.5f / focal_length)) * 2.0f;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PerspectiveLens::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void PerspectiveLens::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PerspectiveLens::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *PerspectiveLens::
+make_from_bam(const FactoryParams &params) {
+  PerspectiveLens *lens = new PerspectiveLens;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  lens->fillin(scan, manager);
+
+  return lens;
+}

+ 6 - 0
panda/src/gobj/perspectiveLens.h

@@ -47,6 +47,12 @@ protected:
   virtual float fov_to_focal_length(float fov, float film_size, bool horiz) const;
   virtual float film_to_fov(float film_size, float focal_length, bool horiz) const;
 
+public:
+  static void register_with_read_factory();
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 36 - 0
panda/src/linmath/lmat_ops_src.I

@@ -99,3 +99,39 @@ operator * (const FLOATNAME(LPoint3) &v, const FLOATNAME(LMatrix4) &m) {
   return m.xform_point(v);
 }
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_write_datagram
+//  Description: Writes the value to the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LMatrix3) &value) {
+  value.write_datagram(dest);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_read_datagram
+//  Description: Reads the value from the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LMatrix3) &result, DatagramIterator &source) {
+  result.read_datagram(source);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_write_datagram
+//  Description: Writes the value to the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LMatrix4) &value) {
+  value.write_datagram(dest);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_read_datagram
+//  Description: Reads the value from the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LMatrix4) &result, DatagramIterator &source) {
+  result.read_datagram(source);
+}

+ 9 - 0
panda/src/linmath/lmat_ops_src.h

@@ -41,6 +41,15 @@ operator * (const FLOATNAME(LVector3) &v, const FLOATNAME(LMatrix4) &m);
 INLINE_LINMATH FLOATNAME(LPoint3)
 operator * (const FLOATNAME(LPoint3) &v, const FLOATNAME(LMatrix4) &m);
 
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LMatrix3) &value);
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LMatrix3) &result, DatagramIterator &source);
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LMatrix4) &value);
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LMatrix4) &result, DatagramIterator &source);
+
 END_PUBLISH
 
 #include "lmat_ops_src.I"

+ 19 - 0
panda/src/linmath/lvec2_ops_src.I

@@ -71,3 +71,22 @@ normalize(const FLOATNAME(LVector2) &v) {
   v1.normalize();
   return v1;
 }
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_write_datagram
+//  Description: Writes the value to the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase2) &value) {
+  value.write_datagram(dest);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_read_datagram
+//  Description: Reads the value from the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase2) &result, DatagramIterator &source) {
+  result.read_datagram(source);
+}

+ 5 - 0
panda/src/linmath/lvec2_ops_src.h

@@ -46,6 +46,11 @@ length(const FLOATNAME(LVector2) &a);
 INLINE_LINMATH FLOATNAME(LVector2)
 normalize(const FLOATNAME(LVector2) &v);
 
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase2) &value);
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase2) &result, DatagramIterator &source);
+
 
 #include "lvec2_ops_src.I"
 

+ 18 - 0
panda/src/linmath/lvec3_ops_src.I

@@ -97,3 +97,21 @@ normalize(const FLOATNAME(LVector3) &v) {
   v1.normalize();
   return v1;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_write_datagram
+//  Description: Writes the value to the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase3) &value) {
+  value.write_datagram(dest);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_read_datagram
+//  Description: Reads the value from the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase3) &result, DatagramIterator &source) {
+  result.read_datagram(source);
+}

+ 5 - 0
panda/src/linmath/lvec3_ops_src.h

@@ -54,4 +54,9 @@ length(const FLOATNAME(LVector3) &a);
 INLINE_LINMATH FLOATNAME(LVector3)
 normalize(const FLOATNAME(LVector3) &v);
 
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase3) &value);
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase3) &result, DatagramIterator &source);
+
 #include "lvec3_ops_src.I"

+ 18 - 0
panda/src/linmath/lvec4_ops_src.I

@@ -75,3 +75,21 @@ normalize(const FLOATNAME(LVector4) &v) {
   v1.normalize();
   return v1;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_write_datagram
+//  Description: Writes the value to the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase4) &value) {
+  value.write_datagram(dest);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: generic_read_datagram
+//  Description: Reads the value from the datagram.
+////////////////////////////////////////////////////////////////////
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase4) &result, DatagramIterator &source) {
+  result.read_datagram(source);
+}

+ 5 - 0
panda/src/linmath/lvec4_ops_src.h

@@ -47,5 +47,10 @@ length(const FLOATNAME(LVector4) &a);
 INLINE_LINMATH FLOATNAME(LVector4)
 normalize(const FLOATNAME(LVector4) &v);
 
+INLINE_LINMATH void
+generic_write_datagram(Datagram &dest, const FLOATNAME(LVecBase4) &value);
+INLINE_LINMATH void
+generic_read_datagram(FLOATNAME(LVecBase4) &result, DatagramIterator &source);
+
 
 #include "lvec4_ops_src.I"

+ 4 - 0
panda/src/mathutil/config_mathutil.cxx

@@ -46,5 +46,9 @@ ConfigureFn(config_mathutil) {
   EventStoreVec2::init_type("EventStoreVec2");
   EventStoreVec3::init_type("EventStoreVec3");
   EventStoreMat4::init_type("EventStoreMat4");
+
+  EventStoreVec2::register_with_read_factory();
+  EventStoreVec3::register_with_read_factory();
+  EventStoreMat4::register_with_read_factory();
 }
 

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

@@ -54,7 +54,9 @@ PUBLISHED:
   bool open_read(const Filename &bam_filename, bool report_errors = true);
   bool open_read(istream &in, const string &bam_filename = "stream",
                  bool report_errors = true);
+
   TypedWritable *read_object();
+
   bool is_eof() const;
   bool resolve();
 

+ 0 - 1
panda/src/pgraph/config_pgraph.cxx

@@ -226,7 +226,6 @@ init_libpgraph() {
   PosHprScaleLerpFunctor::init_type();
   ColorLerpFunctor::init_type();
   ColorScaleLerpFunctor::init_type();
-  EventStoreTransform::init_type();
 
   AlphaTestAttrib::register_with_read_factory();
   AmbientLight::register_with_read_factory();

+ 2 - 3
panda/src/pgraph/lensNode.cxx

@@ -141,8 +141,7 @@ void LensNode::
 write_datagram(BamWriter *manager, Datagram &dg) {
   PandaNode::write_datagram(manager, dg);
 
-  // We should actually write out the lens.  Easy to do, but not
-  // immediately pressing; I hope no one gets burned by the omission.
+  manager->write_pointer(dg, _lens);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -155,7 +154,7 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 int LensNode::
 complete_pointers(TypedWritable **p_list, BamReader *manager) {
   int pi = PandaNode::complete_pointers(p_list, manager);
-
+  _lens = DCAST(Lens, p_list[pi++]);
   return pi;
 }
 

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

@@ -2046,6 +2046,27 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   manager->write_cdata(dg, _cycler);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::write_recorder
+//       Access: Public
+//  Description: This method is provided for the benefit of classes
+//               (like MouseRecorder) that inherit from PandaMode and
+//               also RecorderBase.  It's not virtual at this level
+//               since it doesn't need to be (it's called up from the
+//               derived class).
+//
+//               This method acts very like write_datagram, but it
+//               writes the node as appropriate for writing a
+//               RecorderBase object as described in the beginning of
+//               a session file, meaning it doesn't need to write
+//               things such as children.  It balances with
+//               fillin_recorder().
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+write_recorder(BamWriter *, Datagram &dg) {
+  dg.add_string(get_name());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::make_from_bam
 //       Access: Protected, Static
@@ -2082,3 +2103,18 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   manager->read_cdata(scan, _cycler);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::fillin_recorder
+//       Access: Protected
+//  Description: This internal function is called by make_recorder (in
+//               classes derived from RecorderBase, such as
+//               MouseRecorder) to read in all of the relevant data
+//               from the session file.  It balances with
+//               write_recorder().
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+fillin_recorder(DatagramIterator &scan, BamReader *) {
+  string name = scan.get_string();
+  set_name(name);
+}

+ 2 - 0
panda/src/pgraph/pandaNode.h

@@ -376,10 +376,12 @@ public:
 public:
   static void register_with_read_factory();
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  void write_recorder(BamWriter *manager, Datagram &dg);
 
 protected:
   static TypedWritable *make_from_bam(const FactoryParams &params);
   void fillin(DatagramIterator &scan, BamReader *manager);
+  void fillin_recorder(DatagramIterator &scan, BamReader *manager);
   
 public:
   static TypeHandle get_class_type() {

+ 0 - 31
panda/src/pgraph/transformState.I

@@ -582,34 +582,3 @@ is_destructing() const {
   return false;
 #endif
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: EventStoreTransform::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE EventStoreTransform::
-EventStoreTransform(const TransformState *value) :
-  _value(value)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EventStoreTransform::set_value
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE void EventStoreTransform::
-set_value(const TransformState *value) {
-  _value = value;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EventStoreTransform::get_value
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE const TransformState *EventStoreTransform::
-get_value() const {
-  return _value;
-}

+ 0 - 11
panda/src/pgraph/transformState.cxx

@@ -27,7 +27,6 @@
 TransformState::States *TransformState::_states = NULL;
 CPT(TransformState) TransformState::_identity_state;
 TypeHandle TransformState::_type_handle;
-TypeHandle EventStoreTransform::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::Constructor
@@ -1326,13 +1325,3 @@ fillin(DatagramIterator &scan, BamReader *manager) {
     _mat.read_datagram(scan);
   }
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: EventStoreTransform::output
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void EventStoreTransform::
-output(ostream &out) const {
-  out << *_value;
-}

+ 0 - 34
panda/src/pgraph/transformState.h

@@ -244,40 +244,6 @@ INLINE ostream &operator << (ostream &out, const TransformState &state) {
   return out;
 }
 
-
-////////////////////////////////////////////////////////////////////
-//       Class : EventStoreTransform
-// Description : This class is used to pass TransformState pointers as
-//               parameters to events, or as elements on a data graph.
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA EventStoreTransform : public EventStoreValueBase {
-public:
-  INLINE EventStoreTransform(const TransformState *value);
-  INLINE void set_value(const TransformState *value);
-  INLINE const TransformState *get_value() const;
-
-  virtual void output(ostream &out) const;
-
-  CPT(TransformState) _value;
-
-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;
-  }
-  static void init_type() {
-    EventStoreValueBase::init_type();
-    register_type(_type_handle, "EventStoreTransform",
-                  EventStoreValueBase::get_class_type());
-  }
-
-private:
-  static TypeHandle _type_handle;
-};
-
 #include "transformState.I"
 
 #endif

+ 8 - 8
panda/src/pgui/pgMouseWatcherParameter.h

@@ -22,20 +22,20 @@
 #include "pandabase.h"
 
 #include "mouseWatcherParameter.h"
-#include "typedReferenceCount.h"
+#include "typedWritableReferenceCount.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PGMouseWatcherParameter
 // Description : This specialization on MouseWatcherParameter allows
 //               us to tag on additional elements to events for the
 //               gui system, and also inherits from
-//               TypedReferenceCount so we can attach this thing to an
+//               TypedWritableReferenceCount so we can attach this thing to an
 //               event.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA PGMouseWatcherParameter : public TypedReferenceCount, public MouseWatcherParameter {
-  // For now, this must inherit from TypedReferenceCount on the left,
-  // because MSVC++ wants to make that base class be the one at the
-  // front of the structure, not MouseWatcherParameter for some
+class EXPCL_PANDA PGMouseWatcherParameter : public TypedWritableReferenceCount, public MouseWatcherParameter {
+  // For now, this must inherit from TypedWritableReferenceCount on
+  // the left, because MSVC++ wants to make that base class be the one
+  // at the front of the structure, not MouseWatcherParameter for some
   // reason, and interrogate assumes that whichever base class is on
   // the left will be at the front of the structure.
 public:
@@ -52,9 +52,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    TypedReferenceCount::init_type();
+    TypedWritableReferenceCount::init_type();
     register_type(_type_handle, "PGMouseWatcherParameter",
-                  TypedReferenceCount::get_class_type());
+                  TypedWritableReferenceCount::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 5 - 9
panda/src/putil/Sources.pp

@@ -1,6 +1,6 @@
 #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
                   dtoolutil:c dtoolbase:c dtool:m
-#define LOCAL_LIBS express event pandabase
+#define LOCAL_LIBS express pandabase
 
 #begin lib_target
   #define TARGET putil
@@ -10,8 +10,7 @@
   #define SOURCES \
     bam.h bamReader.I bamReader.N bamReader.h bamReaderParam.I \
     bamReaderParam.h bamWriter.I bamWriter.h bitMask.I \
-    bitMask.h buttonEvent.I buttonEvent.h \
-    buttonEventList.I buttonEventList.h \
+    bitMask.h \
     buttonHandle.I \
     buttonHandle.h buttonRegistry.I buttonRegistry.h \
     collideMask.h \
@@ -53,8 +52,6 @@
     
  #define INCLUDED_SOURCES \
     bamReader.cxx bamReaderParam.cxx bamWriter.cxx bitMask.cxx \
-    buttonEvent.cxx \
-    buttonEventList.cxx \
     buttonHandle.cxx buttonRegistry.cxx \
     config_util.cxx configurable.cxx \
     cycleData.cxx \
@@ -83,9 +80,7 @@
 
   #define INSTALL_HEADERS \
     bam.h bamReader.I bamReader.h bamReaderParam.I bamReaderParam.h \
-    bamWriter.I bamWriter.h bitMask.I bitMask.h buttonEvent.I \
-    buttonEvent.h \
-    buttonEventList.I buttonEventList.h \
+    bamWriter.I bamWriter.h bitMask.I bitMask.h \
     buttonHandle.I buttonHandle.h buttonRegistry.I \
     buttonRegistry.h collideMask.h \
     compareTo.I compareTo.h \
@@ -112,7 +107,8 @@
     pipelineCycler.h pipelineCycler.I \
     pipelineCyclerBase.h pipelineCyclerBase.I \
     pta_double.h \
-    pta_float.h pta_int.h pta_ushort.h string_utils.I \
+    pta_float.h pta_int.h pta_ushort.h \
+    string_utils.I \
     string_utils.h timedCycle.I timedCycle.h typedWritable.I \
     typedWritable.h typedWritableReferenceCount.I \
     typedWritableReferenceCount.h updateSeq.I updateSeq.h \

+ 3 - 2
panda/src/putil/bamReader.I

@@ -83,10 +83,11 @@ get_current_minor_ver() const {
 //  Description: Returns the global WritableFactory for generating
 //               TypedWritable objects
 ////////////////////////////////////////////////////////////////////
-INLINE WritableFactory* BamReader::
+INLINE WritableFactory *BamReader::
 get_factory() {
-  if (_factory == NullFactory)
+  if (_factory == (WritableFactory *)NULL) {
     create_factory();
+  }
   return _factory;
 }
 

+ 161 - 16
panda/src/putil/bamReader.cxx

@@ -25,6 +25,8 @@
 #include "config_util.h"
 #include "pipelineCyclerBase.h"
 
+TypeHandle BamReader::_remove_flag;
+
 WritableFactory *BamReader::_factory = (WritableFactory*)0L;
 BamReader *const BamReader::Null = (BamReader*)0L;
 WritableFactory *const BamReader::NullFactory = (WritableFactory*)0L;
@@ -48,6 +50,8 @@ BamReader(DatagramGenerator *generator)
   _now_creating = _created_objs.end();
   _reading_cycler = (PipelineCyclerBase *)NULL;
   _pta_id = -1;
+  _long_object_id = false;
+  _long_pta_id = false;
 }
 
 
@@ -78,7 +82,7 @@ init() {
     return false;
   }
 
-  if (!_source->get_datagram(header)) {
+  if (!get_datagram(header)) {
     bam_cat.error()
       << "Unable to read Bam header.\n";
     return false;
@@ -124,6 +128,43 @@ init() {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::set_aux_data
+//       Access: Public
+//  Description: Associates an arbitrary pointer to the bam reader
+//               with the indicated name.  The name is an arbitrary
+//               user-defined key to access the data later.  This data
+//               is typically queried by objects reading themselves
+//               from the bam file; this is intended to provide some
+//               context information to objects in the bam file.  Set
+//               the aux data to NULL to remove it.
+////////////////////////////////////////////////////////////////////
+void BamReader::
+set_aux_data(const string &name, void *data) {
+  if (data == (void *)NULL) {
+    _aux_data.erase(name);
+  } else {
+    _aux_data[name] = data;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::get_aux_data
+//       Access: Public
+//  Description: Returns the pointer previously associated with the
+//               bam reader by a previous call to set_aux_data(), or
+//               NULL if the data with the indicated key has not been
+//               set.
+////////////////////////////////////////////////////////////////////
+void *BamReader::
+get_aux_data(const string &name) const {
+  AuxData::const_iterator di = _aux_data.find(name);
+  if (di != _aux_data.end()) {
+    return (*di).second;
+  }
+  return NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::read_object
 //       Access: Public
@@ -237,6 +278,7 @@ resolve() {
 
       CreatedObjs::iterator ci = _created_objs.find(object_id);
       nassertr(ci != _created_objs.end(), false);
+
       CreatedObj &created_obj = (*ci).second;
       
       TypedWritable *object_ptr = created_obj._ptr;
@@ -425,7 +467,7 @@ read_pointer(DatagramIterator &scan) {
   int requestor_id = (*_now_creating).first;
 
   // Read the object ID, and associate it with the requesting object.
-  int object_id = scan.get_uint16();
+  int object_id = read_object_id(scan);
 
   if (_reading_cycler == (PipelineCyclerBase *)NULL) {
     // This is not being read within a read_cdata() call.
@@ -471,7 +513,7 @@ read_pointers(DatagramIterator &scan, int count) {
 ////////////////////////////////////////////////////////////////////
 void BamReader::
 skip_pointer(DatagramIterator &scan) {
-  scan.get_uint16();
+  read_object_id(scan);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -597,7 +639,7 @@ finalize_now(TypedWritable *whom) {
 void *BamReader::
 get_pta(DatagramIterator &scan) {
   nassertr(_pta_id == -1, (void *)NULL);
-  int id = scan.get_uint16();
+  int id = read_pta_id(scan);
 
   if (id == 0) {
     // As always, a 0 ID indicates a NULL pointer.  The caller will
@@ -641,12 +683,92 @@ register_pta(void *ptr) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::free_object_ids
+//       Access: Private
+//  Description: Handles a record that begins with the _remove_flag
+//               TypeHandle; this contains a list of object ID's that
+//               will no longer be used in this file and can safely be
+//               removed.
+////////////////////////////////////////////////////////////////////
+void BamReader::
+free_object_ids(DatagramIterator &scan) {
+  // We have to fully complete any objects before we remove them.
+  // Might as well try to complete everything before we get started.
+  resolve();
+
+  while (scan.get_remaining_size() > 0) {
+    int object_id = read_object_id(scan);
+
+    CreatedObjs::iterator ci = _created_objs.find(object_id);
+    if (ci == _created_objs.end()) {
+      util_cat.warning()
+        << "Bam file suggests eliminating object_id " << object_id
+        << ", already gone.\n";
+    } else {
+
+      // Make sure the object was successfully completed.
+      ObjectPointers::iterator oi = _object_pointers.find(object_id);
+      if (oi != _object_pointers.end()) {
+        util_cat.warning()
+          << "Unable to resolve object " << object_id
+          << " before removing from table.\n";
+      }
+
+      _created_objs.erase(ci);
+    }
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::read_object_id
+//       Access: Private
+//  Description: Reads an object id from the datagram.
+////////////////////////////////////////////////////////////////////
+int BamReader::
+read_object_id(DatagramIterator &scan) {
+  int object_id;
+
+  if (_long_object_id) {
+    object_id = scan.get_uint32();
+
+  } else {
+    object_id = scan.get_uint16();
+    if (object_id == 0xffff) {
+      _long_object_id = true;
+    }
+  }
+
+  return object_id;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::read_pta_id
+//       Access: Private
+//  Description: Reads an pta id from the datagram.
+////////////////////////////////////////////////////////////////////
+int BamReader::
+read_pta_id(DatagramIterator &scan) {
+  int pta_id;
+
+  if (_long_pta_id) {
+    pta_id = scan.get_uint32();
 
+  } else {
+    pta_id = scan.get_uint16();
+    if (pta_id == 0xffff) {
+      _long_pta_id = true;
+    }
+  }
+
+  return pta_id;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamReader::p_read_object
 //       Access: Private
-//  Description: The private implementation of read_object(), this
+//  Description: The private implementation of read_object(); this
 //               reads an object from the file and returns its object
 //               ID.
 ////////////////////////////////////////////////////////////////////
@@ -654,14 +776,9 @@ int BamReader::
 p_read_object() {
   Datagram packet;
 
-  if (_source->is_error()) {
-    return 0;
-  }
-
   // First, read a datagram for the object.
-  if (!_source->get_datagram(packet)) {
+  if (!get_datagram(packet)) {
     // When we run out of datagrams, we're at the end of the file.
-
     if (bam_cat.is_debug()) {
       bam_cat.debug()
         << "Reached end of bam source.\n";
@@ -678,10 +795,24 @@ p_read_object() {
   // object.
 
   TypeHandle type = read_handle(scan);
-  int object_id = scan.get_uint16();
 
-  // There are two cases.  Either this is a new object definition, or
-  // this is a reference to an object that was previously defined.
+  if (type == _remove_flag) {
+    // The _remove_flag TypeHandle 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);
+
+    // Now that we've freed all of the object id's indicate, read the
+    // next object id in the stream.  It's easiest to do this by
+    // calling recursively.
+    return p_read_object();
+  }
+
+  int object_id = read_object_id(scan);
+
+  // There are two cases (not counting the special _remove_flag case,
+  // above).  Either this is a new object definition, or this is a
+  // reference to an object that was previously defined.
 
   // We use the TypeHandle to differentiate these two cases.  By
   // convention, we write a TypeHandle::none() to the Bam file when we
@@ -697,7 +828,7 @@ p_read_object() {
   if (type != TypeHandle::none()) {
     // Now we are going to read and create a new object.
 
-    // Defined the parameters for passing to the object factory.
+    // Define the parameters for passing to the object factory.
     FactoryParams fparams;
     fparams.add_param(new BamReaderParam(scan, this));
 
@@ -796,7 +927,6 @@ resolve_object_pointers(TypedWritable *object, const vector_int &pointer_ids) {
   // in, we can't resolve this object--we can't do anything for a
   // given object until we have *all* outstanding pointers for
   // that object.
-  
   bool is_complete = true;
   vector_typedWritable references;
   
@@ -932,3 +1062,18 @@ finalize() {
     fi = _finalize_list.begin();
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamReader::get_datagram
+//       Access: Private
+//  Description: Reads a single datagram from the stream.  Returns
+//               true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool BamReader::
+get_datagram(Datagram &datagram) {
+  if (_source->is_error()) {
+    return false;
+  }
+
+  return _source->get_datagram(datagram);
+}

+ 21 - 1
panda/src/putil/bamReader.h

@@ -29,6 +29,7 @@
 #include "factory.h"
 #include "vector_int.h"
 #include "pset.h"
+#include "pmap.h"
 #include "dcast.h"
 
 #include <algorithm>
@@ -92,11 +93,14 @@ public:
   static WritableFactory *const NullFactory;
 
   // The primary interface for a caller.
-
   BamReader(DatagramGenerator *generator);
   ~BamReader();
 
   bool init();
+
+  void set_aux_data(const string &name, void *data);
+  void *get_aux_data(const string &name) const;
+
   TypedWritable *read_object();
   INLINE bool is_eof() const;
   bool resolve();
@@ -107,6 +111,10 @@ public:
   INLINE int get_current_major_ver() const;
   INLINE int get_current_minor_ver() const;
 
+  // This special TypeHandle is written to the bam file to indicate an
+  // object id is no longer needed.
+  static TypeHandle _remove_flag;
+
 public:
   // Functions to support classes that read themselves from the Bam.
 
@@ -135,15 +143,23 @@ private:
   INLINE static void create_factory();
 
 private:
+  void free_object_ids(DatagramIterator &scan);
+  int read_object_id(DatagramIterator &scan);
+  int read_pta_id(DatagramIterator &scan);
   int p_read_object();
   bool resolve_object_pointers(TypedWritable *object, const vector_int &pointer_ids);
   bool resolve_cycler_pointers(PipelineCyclerBase *cycler, const vector_int &pointer_ids);
   void finalize();
 
+  bool get_datagram(Datagram &datagram);
+
 private:
   static WritableFactory *_factory;
 
   DatagramGenerator *_source;
+  
+  bool _long_object_id;
+  bool _long_pta_id;
 
   // This maps the type index numbers encountered within the Bam file
   // to actual TypeHandles.
@@ -202,6 +218,10 @@ private:
   typedef pset<TypeHandle> NewTypes;
   static NewTypes _new_types;
 
+  // This is used in support of set_aux_data() and get_aux_data().
+  typedef pmap<string, void *> AuxData;
+  AuxData _aux_data;
+
   int _file_major, _file_minor;
   static const int _cur_major;
   static const int _cur_minor;

+ 107 - 21
panda/src/putil/bamWriter.cxx

@@ -25,6 +25,8 @@
 #include "bamWriter.h"
 #include "bamReader.h"
 
+#include <algorithm>
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::Constructor
 //       Access: Public
@@ -43,6 +45,16 @@ BamWriter(DatagramSink *sink) :
 ////////////////////////////////////////////////////////////////////
 BamWriter::
 ~BamWriter() {
+  // Tell all the TypedWritables whose pointer we are still keeping to
+  // forget about us.
+  StateMap::iterator si;
+  for (si = _state_map.begin(); si != _state_map.end(); ++si) {
+    TypedWritable *object = (TypedWritable *)(*si).first;
+    TypedWritable::BamWriters::iterator wi = 
+      find(object->_bam_writers.begin(), object->_bam_writers.end(), this);
+    nassertv(wi != object->_bam_writers.end());
+    object->_bam_writers.erase(wi);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -60,7 +72,9 @@ init() {
   // Initialize the next object and PTA ID's.  These start counting at
   // 1, since 0 is reserved for NULL.
   _next_object_id = 1;
+  _long_object_id = false;
   _next_pta_id = 1;
+  _long_pta_id = false;
 
   // Write out the current major and minor BAM file version numbers.
   Datagram header;
@@ -105,6 +119,24 @@ write_object(const TypedWritable *object) {
   int object_id = enqueue_object(object);
   nassertr(object_id != 0, false);
 
+  // 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);
+
+    FreedObjectIds::iterator fi;
+    for (fi = _freed_object_ids.begin(); fi != _freed_object_ids.end(); ++fi) {
+      write_object_id(dg, (*fi));
+    }
+    _freed_object_ids.clear();
+
+    if (!_target->put_datagram(dg)) {
+      util_cat.error()
+        << "Unable to write data to output.\n";
+      return false;
+    }
+  }
+
   // 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.
@@ -151,7 +183,7 @@ write_object(const TypedWritable *object) {
       }
 
       write_handle(dg, type);
-      dg.add_uint16(object_id);
+      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
@@ -170,12 +202,12 @@ write_object(const TypedWritable *object) {
       // BamReader that this is a previously-written object.
 
       write_handle(dg, TypeHandle::none());
-      dg.add_uint16(object_id);
+      write_object_id(dg, object_id);
     }
 
     if (!_target->put_datagram(dg)) {
       util_cat.error()
-        << "Unable to write datagram to file.\n";
+        << "Unable to write data to output.\n";
       return false;
     }
   }
@@ -215,7 +247,7 @@ write_pointer(Datagram &packet, const TypedWritable *object) {
   // If the pointer is NULL, we always simply write a zero for an
   // object ID and leave it at that.
   if (object == (const TypedWritable *)NULL) {
-    packet.add_uint16(0);
+    write_object_id(packet, 0);
 
   } else {
     StateMap::iterator si = _state_map.find(object);
@@ -223,12 +255,12 @@ write_pointer(Datagram &packet, const TypedWritable *object) {
       // We have not written this pointer out yet.  This means we must
       // queue the object definition up for later.
       int object_id = enqueue_object(object);
-      packet.add_uint16(object_id);
+      write_object_id(packet, object_id);
 
     } else {
       // We have already assigned this pointer an ID; thus, we can
       // simply write out the ID.
-      packet.add_uint16((*si).second._object_id);
+      write_object_id(packet, (*si).second._object_id);
     }
   }
 }
@@ -273,7 +305,7 @@ register_pta(Datagram &packet, const void *ptr) {
   if (ptr == (const void *)NULL) {
     // A zero for the PTA ID indicates a NULL pointer.  This is a
     // special case.
-    packet.add_uint16(0);
+    write_pta_id(packet, 0);
 
     // We return false to indicate the user must now write out the
     // "definition" of the NULL pointer.  This is necessary because of
@@ -292,14 +324,10 @@ register_pta(Datagram &packet, const void *ptr) {
     int pta_id = _next_pta_id;
     _next_pta_id++;
 
-    // Make sure our PTA ID will fit within the PN_uint16 we have
-    // allocated for it.
-    nassertr(pta_id <= 65535, 0);
-
     bool inserted = _pta_map.insert(PTAMap::value_type(ptr, pta_id)).second;
     nassertr(inserted, false);
 
-    packet.add_uint16(pta_id);
+    write_pta_id(packet, pta_id);
 
     // Return false to indicate the caller must now write out the
     // array definition.
@@ -308,7 +336,7 @@ register_pta(Datagram &packet, const void *ptr) {
   } else {
     // We have encountered this pointer before.
     int pta_id = (*pi).second;
-    packet.add_uint16(pta_id);
+    write_pta_id(packet, pta_id);
 
     // Return true to indicate the caller need do nothing further.
     return true;
@@ -336,7 +364,7 @@ write_handle(Datagram &packet, TypeHandle type) {
   int index = type.get_index();
 
   // Also make sure the index number fits within a PN_uint16.
-  nassertv(index <= 65535);
+  nassertv(index <= 0xffff);
 
   packet.add_uint16(index);
 
@@ -361,6 +389,70 @@ write_handle(Datagram &packet, TypeHandle type) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::object_destructs
+//       Access: Private
+//  Description: This is called by the TypedWritable destructor.  It
+//               should remove the pointer from any structures that
+//               keep a reference to it, and also write a flag to the
+//               bam file (if it is open) so that a reader will know
+//               the object id will no longer be used.
+////////////////////////////////////////////////////////////////////
+void BamWriter::
+object_destructs(TypedWritable *object) {
+  StateMap::iterator si = _state_map.find(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);
+
+    int object_id = (*si).second._object_id;
+    _freed_object_ids.push_back(object_id);
+
+    _state_map.erase(si);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::write_object_id
+//       Access: Private
+//  Description: Writes the indicated object id to the datagram.
+////////////////////////////////////////////////////////////////////
+void BamWriter::
+write_object_id(Datagram &dg, int object_id) {
+  if (_long_object_id) {
+    dg.add_uint32(object_id);
+    
+  } else {
+    dg.add_uint16(object_id);
+    // Once we fill up our uint16, we write all object id's
+    // thereafter with a uint32.
+    if (object_id == 0xffff) {
+      _long_object_id = true;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BamWriter::write_pta_id
+//       Access: Private
+//  Description: Writes the indicated pta id to the datagram.
+////////////////////////////////////////////////////////////////////
+void BamWriter::
+write_pta_id(Datagram &dg, int pta_id) {
+  if (_long_pta_id) {
+    dg.add_uint32(pta_id);
+    
+  } else {
+    dg.add_uint16(pta_id);
+    // Once we fill up our uint16, we write all pta id's
+    // thereafter with a uint32.
+    if (pta_id == 0xffff) {
+      _long_pta_id = true;
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BamWriter::enqueue_object
 //       Access: Private
@@ -392,28 +484,22 @@ enqueue_object(const TypedWritable *object) {
   // We need to assign a unique index number to every object we write
   // out.  Has this object been assigned a number yet?
   int object_id;
-  //  bool already_written;
 
   StateMap::iterator si = _state_map.find(object);
   if (si == _state_map.end()) {
     // No, it hasn't, so assign it the next number in sequence
     // arbitrarily.
     object_id = _next_object_id;
-    //    already_written = false;
-
-    // Make sure our object ID will fit within the PN_uint16 we have
-    // allocated for it.
-    nassertr(object_id <= 65535, 0);
 
     bool inserted =
       _state_map.insert(StateMap::value_type(object, StoreState(_next_object_id))).second;
     nassertr(inserted, false);
+    ((TypedWritable *)object)->_bam_writers.push_back(this);
     _next_object_id++;
 
   } else {
     // Yes, it has; get the object ID.
     object_id = (*si).second._object_id;
-    //    already_written = (*si).second._written;
   }
 
   _object_queue.push_back(object);

+ 18 - 4
panda/src/putil/bamWriter.h

@@ -25,6 +25,9 @@
 #include "typedWritable.h"
 #include "datagramSink.h"
 #include "pdeque.h"
+#include "pset.h"
+#include "pmap.h"
+#include "vector_int.h"
 
 struct PipelineCyclerBase;
 
@@ -71,7 +74,7 @@ struct PipelineCyclerBase;
 //               See also BamFile, which defines a higher-level
 //               interface to read and write Bam files on disk.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA BamWriter{
+class EXPCL_PANDA BamWriter {
 public:
   BamWriter(DatagramSink *sink);
   ~BamWriter();
@@ -90,11 +93,12 @@ public:
   bool register_pta(Datagram &packet, const void *ptr);
   void write_handle(Datagram &packet, TypeHandle type);
 
-  
-
 private:
-  int enqueue_object(const TypedWritable *object);
+  void object_destructs(TypedWritable *object);
 
+  void write_object_id(Datagram &dg, int object_id);
+  void write_pta_id(Datagram &dg, int pta_id);
+  int enqueue_object(const TypedWritable *object);
 
   // This is the set of all TypeHandles already written.
   pset<int> _types_written;
@@ -114,20 +118,30 @@ private:
 
   // This is the next object ID that will be assigned to a new object.
   int _next_object_id;
+  bool _long_object_id;
 
   // This is the queue of objects that need to be written when the
   // current object is finished.
   typedef pdeque<const TypedWritable *> ObjectQueue;
   ObjectQueue _object_queue;
 
+  // This is the set of object_id's that we won't be using any more;
+  // we'll encode this set into the bam stream so the BamReader will
+  // be able to clean up its internal structures.
+  typedef vector_int FreedObjectIds;
+  FreedObjectIds _freed_object_ids;
+
   // These are used by register_pta() to unify multiple references to
   // the same PointerToArray.
   typedef pmap<const void *, int> PTAMap;
   PTAMap _pta_map;
   int _next_pta_id;
+  bool _long_pta_id;
 
   // The destination to write all the output to.
   DatagramSink *_target;
+
+  friend class TypedWritable;
 };
 
 #include "bamWriter.I"

+ 0 - 45
panda/src/putil/buttonEvent.cxx

@@ -1,45 +0,0 @@
-// Filename: buttonEvent.cxx
-// Created by:  drose (01Mar00)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
-//
-// All use of this software is subject to the terms of the Panda 3d
-// Software license.  You should have received a copy of this license
-// along with this source code; you will also find a current copy of
-// the license at http://www.panda3d.org/license.txt .
-//
-// To contact the maintainers of this program write to
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#include "buttonEvent.h"
-
-////////////////////////////////////////////////////////////////////
-//     Function: ButtonEvent::output
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-void ButtonEvent::
-output(ostream &out) const {
-  switch (_type) {
-  case T_down:
-    out << "button " << _button << " down";
-    break;
-
-  case T_resume_down:
-    out << "button " << _button << " resume down";
-    break;
-
-  case T_up:
-    out << "button " << _button << " up";
-    break;
-
-  case T_keystroke:
-    out << "keystroke " << _keycode;
-    break;
-  }
-}

+ 0 - 74
panda/src/putil/buttonEventList.cxx

@@ -1,74 +0,0 @@
-// Filename: buttonEventList.cxx
-// Created by:  drose (12Mar02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
-//
-// All use of this software is subject to the terms of the Panda 3d
-// Software license.  You should have received a copy of this license
-// along with this source code; you will also find a current copy of
-// the license at http://www.panda3d.org/license.txt .
-//
-// To contact the maintainers of this program write to
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#include "buttonEventList.h"
-#include "modifierButtons.h"
-#include "indent.h"
-
-TypeHandle ButtonEventList::_type_handle;
-
-////////////////////////////////////////////////////////////////////
-//     Function: ButtonEventList::update_mods
-//       Access: Public
-//  Description: Updates the indicated ModifierButtons object with all
-//               of the button up/down transitions indicated in the
-//               list.
-////////////////////////////////////////////////////////////////////
-void ButtonEventList::
-update_mods(ModifierButtons &mods) const {
-  Events::const_iterator ei;
-  for (ei = _events.begin(); ei != _events.end(); ++ei) {
-    mods.add_event(*ei);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ButtonEventList::output
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void ButtonEventList::
-output(ostream &out) const {
-  if (_events.empty()) {
-    out << "(no buttons)";
-  } else {
-    Events::const_iterator ei;
-    ei = _events.begin();
-    out << "(" << (*ei);
-    ++ei;
-    while (ei != _events.end()) {
-      out << " " << (*ei);
-      ++ei;
-    }
-    out << ")";
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ButtonEventList::write
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void ButtonEventList::
-write(ostream &out, int indent_level) const {
-  indent(out, indent_level) << _events.size() << " events:\n";
-  Events::const_iterator ei;
-  for (ei = _events.begin(); ei != _events.end(); ++ei) {
-    indent(out, indent_level + 2) << (*ei) << "\n";
-  }
-}

+ 2 - 2
panda/src/putil/config_util.cxx

@@ -18,7 +18,6 @@
 
 #include "config_util.h"
 #include "bamReaderParam.h"
-#include "buttonEventList.h"
 #include "clockObject.h"
 #include "configurable.h"
 #include "datagram.h"
@@ -43,7 +42,6 @@ NotifyCategoryDef(bam, util_cat);
 
 ConfigureFn(config_util) {
   BamReaderParam::init_type();
-  ButtonEventList::init_type();
   Configurable::init_type();
   Datagram::init_type();
   FactoryParam::init_type();
@@ -58,6 +56,8 @@ ConfigureFn(config_util) {
 
   KeyboardButton::init_keyboard_buttons();
   MouseButton::init_mouse_buttons();
+
+  register_type(BamReader::_remove_flag, "remove");
 }
 
 

+ 11 - 25
panda/src/putil/datagramInputFile.cxx

@@ -23,14 +23,9 @@
 #include "config_util.h"
 #include "config_express.h"
 #include "virtualFileSystem.h"
-
+#include "streamReader.h"
 #include "datagramInputFile.h"
 
-//#define SKYLER_TIMER 1
-#ifdef SKYLER_TIMER //[
-  EXPCL_PANDAEXPRESS ProfileTimer Skyler_timer_file;
-#endif //]
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DatagramInputFile::open
 //       Access: Public
@@ -135,26 +130,22 @@ read_header(string &header, size_t num_bytes) {
 ////////////////////////////////////////////////////////////////////
 bool DatagramInputFile::
 get_datagram(Datagram &data) {
-  #ifdef SKYLER_TIMER //[
-    Skyler_timer_file.on();
-  #endif //]
   nassertr(_in != (istream *)NULL, false);
   _read_first_datagram = true;
 
-  // First, get the size of the upcoming datagram.  We do this with
-  // the help of a second datagram.
-  char sizebuf[sizeof(PN_uint32)];
-  _in->read(sizebuf, sizeof(PN_uint32));
+  // First, get the size of the upcoming datagram.
+  StreamReader reader(_in, false);
+  PN_uint32 num_bytes = reader.get_uint32();
   if (_in->fail() || _in->eof()) {
-    #ifdef SKYLER_TIMER //[
-      Skyler_timer_file.off("DatagramInputFile::get_datagram");
-    #endif //]
     return false;
   }
 
-  Datagram size(sizebuf, sizeof(PN_uint32));
-  DatagramIterator di(size);
-  PN_uint32 num_bytes = di.get_uint32();
+  if (num_bytes == 0) {
+    // A special case for a zero-length datagram: no need to try to
+    // read any data.
+    data.clear();
+    return true;
+  }
 
   // Now, read the datagram itself.
   char *buffer = new char[num_bytes];
@@ -164,17 +155,12 @@ get_datagram(Datagram &data) {
   if (_in->fail() || _in->eof()) {
     _error = true;
     delete[] buffer;
-    #ifdef SKYLER_TIMER //[
-      Skyler_timer_file.off("DatagramInputFile::get_datagram");
-    #endif //]
     return false;
   }
 
   data = Datagram(buffer, num_bytes);
   delete[] buffer;
-  #ifdef SKYLER_TIMER //[
-    Skyler_timer_file.off("DatagramInputFile::get_datagram");
-  #endif //]
+
   return true;
 }
 

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

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "datagramOutputFile.h"
+#include "streamWriter.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DatagramOutputFile::open
@@ -103,11 +104,9 @@ put_datagram(const Datagram &data) {
   nassertr(_out != (ostream *)NULL, false);
   _wrote_first_datagram = true;
 
-  // First, write the size of the upcoming datagram.  We do this with
-  // the help of a second datagram.
-  Datagram size;
-  size.add_uint32(data.get_length());
-  _out->write((const char *)size.get_data(), size.get_length());
+  // First, write the size of the upcoming datagram.
+  StreamWriter writer(_out);
+  writer.add_uint32(data.get_length());
 
   // Now, write the datagram itself.
   _out->write((const char *)data.get_data(), data.get_length());

+ 0 - 20
panda/src/putil/modifierButtons.I

@@ -92,26 +92,6 @@ get_button(int index) const {
   return _button_list[index];
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: ModifierButtons::add_event
-//       Access: Published
-//  Description: Calls button_down() or button_up(), as appropriate,
-//               according to the indicated ButtonEvent.
-////////////////////////////////////////////////////////////////////
-INLINE bool ModifierButtons::
-add_event(const ButtonEvent &event) {
-  switch (event._type) {
-  case ButtonEvent::T_down:
-    return button_down(event._button);
-
-  case ButtonEvent::T_up:
-    return button_up(event._button);
-
-  default:
-    return false;
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ModifierButtons::all_buttons_up
 //       Access: Published

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

@@ -23,7 +23,6 @@
 
 #include "buttonHandle.h"
 #include "pointerToArray.h"
-#include "buttonEvent.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ModifierButtons
@@ -53,7 +52,6 @@ PUBLISHED:
 
   bool button_down(ButtonHandle button);
   bool button_up(ButtonHandle button);
-  INLINE bool add_event(const ButtonEvent &event);
   INLINE void all_buttons_up();
 
   bool is_down(ButtonHandle button) const;

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

@@ -2,8 +2,6 @@
 #include "bamReaderParam.cxx"
 #include "bamWriter.cxx"
 #include "bitMask.cxx"
-#include "buttonEvent.cxx"
-#include "buttonEventList.cxx"
 #include "buttonHandle.cxx"
 #include "buttonRegistry.cxx"
 #include "config_util.cxx"

+ 9 - 1
panda/src/putil/typedWritable.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "typedWritable.h"
+#include "bamWriter.h"
 
 TypeHandle TypedWritable::_type_handle;
 TypedWritable* const TypedWritable::Null = (TypedWritable*)0L;
@@ -26,7 +27,14 @@ TypedWritable* const TypedWritable::Null = (TypedWritable*)0L;
 //       Access: Public, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
-TypedWritable::~TypedWritable() {
+TypedWritable::
+~TypedWritable() {
+  // Remove the object pointer from the BamWriters that reference it.
+  BamWriters::iterator wi;
+  for (wi = _bam_writers.begin(); wi != _bam_writers.end(); ++wi) {
+    BamWriter *writer = (*wi);
+    writer->object_destructs(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 11 - 1
panda/src/putil/typedWritable.h

@@ -21,6 +21,7 @@
 
 #include "typedObject.h"
 #include "vector_typedWritable.h"
+#include "pvector.h"
 
 class BamReader;
 class BamWriter;
@@ -32,7 +33,7 @@ class DatagramIterator;
 // Description : Base class for objects that can be written to and
 //               read from Bam files.
 //               
-//               See Also TypeObject for detailed instructions.
+//               See also TypedObject for detailed instructions.
 ////////////////////////////////////////////////////////////////////
 
 class EXPCL_PANDA TypedWritable : public TypedObject {
@@ -54,6 +55,13 @@ public:
 protected:
   void fillin(DatagramIterator &scan, BamReader *manager);
 
+private:
+  // We need to store a list of the BamWriter(s) that have a reference
+  // to this object, so that we can remove the object from those
+  // tables when it destructs.
+  typedef pvector<BamWriter *> BamWriters;
+  BamWriters _bam_writers;
+
 PUBLISHED:
   static TypeHandle get_class_type() {
     return _type_handle;
@@ -76,6 +84,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class BamWriter;
 };
 
 #include "typedWritable.I"

+ 1 - 4
panda/src/putil/typedWritableReferenceCount.h

@@ -34,7 +34,7 @@
 //               pass around pointers to things which are both
 //               TypedWritables and ReferenceCounters.
 //               
-//               See Also TypeObject for detailed instructions.
+//               See also TypedObject for detailed instructions.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA TypedWritableReferenceCount : public TypedWritable, public ReferenceCount {
 public:
@@ -42,9 +42,6 @@ public:
   INLINE TypedWritableReferenceCount(const TypedWritableReferenceCount &copy);
   INLINE void operator = (const TypedWritableReferenceCount &copy);
 
-public:
-  virtual void write_datagram(BamWriter *, Datagram &) = 0;
-
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 38 - 0
panda/src/recorder/Sources.pp

@@ -0,0 +1,38 @@
+#define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
+                  dtoolutil:c dtoolbase:c dtool:m
+#define LOCAL_LIBS dgraph putil express pandabase
+
+#begin lib_target
+  #define TARGET recorder
+ 
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx   
+  
+  #define SOURCES \
+    config_recorder.h \
+    mouseRecorder.h \
+    recorderBase.h recorderBase.I \
+    recorderController.h recorderController.I \
+    recorderFrame.h recorderFrame.I \
+    recorderHeader.h recorderHeader.I \
+    recorderTable.h recorderTable.I \
+    socketStreamRecorder.h socketStreamRecorder.I
+    
+ #define INCLUDED_SOURCES \
+    config_recorder.cxx \
+    mouseRecorder.cxx \
+    recorderBase.cxx recorderController.cxx \
+    recorderFrame.cxx recorderHeader.cxx recorderTable.cxx \
+    socketStreamRecorder.cxx
+
+  #define INSTALL_HEADERS \
+    mouseRecorder.h \
+    recorderBase.h recorderBase.I \
+    recorderController.h recorderController.I \
+    recorderFrame.h recorderFrame.I \
+    recorderHeader.h recorderHeader.I \
+    recorderTable.h recorderTable.I \
+    socketStreamRecorder.h socketStreamRecorder.I
+
+  #define IGATESCAN all
+
+#end lib_target

+ 47 - 0
panda/src/recorder/config_recorder.cxx

@@ -0,0 +1,47 @@
+// Filename: config_recorder.cxx
+// Created by:  drose (28Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_recorder.h"
+
+#include "mouseRecorder.h"
+#include "recorderController.h"
+#include "recorderFrame.h"
+#include "recorderHeader.h"
+#include "recorderTable.h"
+#include "socketStreamRecorder.h"
+
+#include "dconfig.h"
+
+ConfigureDef(config_recorder);
+NotifyCategoryDef(recorder, "");
+
+ConfigureFn(config_recorder) {
+  MouseRecorder::init_type();
+  RecorderController::init_type();
+  RecorderFrame::init_type();
+  RecorderHeader::init_type();
+  RecorderTable::init_type();
+  ReferenceCount::init_type();
+  SocketStreamRecorder::init_type();
+
+  MouseRecorder::register_with_read_factory();
+  RecorderFrame::register_with_read_factory();
+  RecorderHeader::register_with_read_factory();
+  RecorderTable::register_with_read_factory();
+  SocketStreamRecorder::register_with_read_factory();
+}

+ 29 - 0
panda/src/recorder/config_recorder.h

@@ -0,0 +1,29 @@
+// Filename: config_recorder.h
+// Created by:  drose (28Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_RECORDER_H
+#define CONFIG_RECORDER_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+#include "dconfig.h"
+
+ConfigureDecl(config_recorder, EXPCL_PANDA, EXPTP_PANDA);
+NotifyCategoryDecl(recorder, EXPCL_PANDA, EXPTP_PANDA);
+
+#endif // CONFIG_RECORDER_H

+ 297 - 0
panda/src/recorder/mouseRecorder.cxx

@@ -0,0 +1,297 @@
+// Filename: mouseRecorder.cxx
+// Created by:  drose (24Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "mouseRecorder.h"
+#include "recorderController.h"
+#include "dataNodeTransmit.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+
+TypeHandle MouseRecorder::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+MouseRecorder::
+MouseRecorder(const string &name) : 
+  DataNode(name) 
+{
+  _pixel_xy_input = define_input("pixel_xy", EventStoreVec2::get_class_type());
+  _xy_input = define_input("xy", EventStoreVec2::get_class_type());
+  _button_events_input = define_input("button_events", ButtonEventList::get_class_type());
+
+  _pixel_xy_output = define_output("pixel_xy", EventStoreVec2::get_class_type());
+  _xy_output = define_output("xy", EventStoreVec2::get_class_type());
+  _button_events_output = define_output("button_events", ButtonEventList::get_class_type());
+
+  _live_button_events = new ButtonEventList;
+  _save_button_events = new ButtonEventList;
+
+  _pixel_xy = new EventStoreVec2(LPoint2f(0.0f, 0.0f));
+  _xy = new EventStoreVec2(LPoint2f(0.0f, 0.0f));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+MouseRecorder::
+~MouseRecorder() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::record_frame
+//       Access: Public, Virtual
+//  Description: Records the most recent data collected into the
+//               indicated datagram, and returns true if there is any
+//               interesting data worth recording, or false if the
+//               datagram is meaningless.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+record_frame(BamWriter *manager, Datagram &dg) {
+  nassertv(is_recording());
+  dg.add_bool(_has_mouse);
+  if (_has_mouse) {
+    _mouse_xy.write_datagram(dg);
+    _mouse_pixel_xy.write_datagram(dg);
+  }
+  _save_button_events->write_datagram(manager, dg);
+  _save_button_events->clear();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::play_frame
+//       Access: Public, Virtual
+//  Description: Reloads the most recent data collected from the
+//               indicated datagram.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+play_frame(DatagramIterator &scan, BamReader *manager) {
+  nassertv(is_playing());
+  _has_mouse = scan.get_bool();
+  if (_has_mouse) {
+    _mouse_xy.read_datagram(scan);
+    _mouse_pixel_xy.read_datagram(scan);
+  }
+  ButtonEventList button_events;
+  button_events.fillin(scan, manager);
+  _save_button_events->add_events(button_events);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::output
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+output(ostream &out) const {
+  DataNode::output(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::write
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+write(ostream &out, int indent_level) const {
+  DataNode::write(out, indent_level);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::do_transmit_data
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of transmit_data().  This
+//               function receives an array of input parameters and
+//               should generate an array of output parameters.  The
+//               input parameters may be accessed with the index
+//               numbers returned by the define_input() calls that
+//               were made earlier (presumably in the constructor);
+//               likewise, the output parameters should be set with
+//               the index numbers returned by the define_output()
+//               calls.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
+  bool has_mouse = false;
+  LPoint2f mouse_xy;
+  LPoint2f mouse_pixel_xy;
+
+  _live_button_events->clear();
+
+  if (is_playing()) {
+    // If we're playing back data, copy in the data from a previous
+    // call to play_frame().
+    has_mouse = _has_mouse;
+    mouse_xy = _mouse_xy;
+    mouse_pixel_xy = _mouse_pixel_xy;
+    _live_button_events->add_events(*_save_button_events);
+    _save_button_events->clear();
+
+  } else {
+    // If we're not playing back data, query the data from the data
+    // graph
+
+    if (input.has_data(_xy_input)) {
+      // The mouse is within the window.  Get the current mouse position.
+      const EventStoreVec2 *xy;
+      DCAST_INTO_V(xy, input.get_data(_xy_input).get_ptr());
+      mouse_xy = xy->get_value();
+      DCAST_INTO_V(xy, input.get_data(_pixel_xy_input).get_ptr());
+      mouse_pixel_xy = xy->get_value();
+      has_mouse = true;
+    }
+    
+    // Look for button events.
+    if (input.has_data(_button_events_input)) {
+      const ButtonEventList *button_events;
+      DCAST_INTO_V(button_events, input.get_data(_button_events_input).get_ptr());
+      _live_button_events->add_events(*button_events);
+    }
+  }
+    
+  // Now rebuild the output data for our children.
+
+  if (has_mouse) {
+    // Transmit the mouse position.
+    _pixel_xy->set_value(_mouse_pixel_xy);
+    _xy->set_value(_mouse_xy);
+    output.set_data(_xy_output, EventParameter(_xy));
+    output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
+  }
+
+  if (_live_button_events->get_num_events() != 0) {
+    output.set_data(_button_events_output, EventParameter(_live_button_events));
+  }
+
+  if (is_recording()) {
+    // Save data for the record.
+    _has_mouse = has_mouse;
+    _mouse_xy = mouse_xy;
+    _mouse_pixel_xy = mouse_pixel_xy;
+    _save_button_events->add_events(*_live_button_events);
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               Lens.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+  RecorderController::get_factory()->register_factory(get_class_type(), make_recorder);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  DataNode::write_datagram(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::write_recorder
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for encoding in the session file.  This is very
+//               similar to write_datagram() for TypedWritable
+//               objects, but it is used specifically to write the
+//               Recorder object when generating the session file.  In
+//               many cases, it will be the same as write_datagram().
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+write_recorder(BamWriter *manager, Datagram &dg) {
+  RecorderBase::write_recorder(manager, dg);
+  DataNode::write_recorder(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type Lens is encountered
+//               in the Bam file.  It should create the Lens
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *MouseRecorder::
+make_from_bam(const FactoryParams &params) {
+  MouseRecorder *node = new MouseRecorder("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::make_recorder
+//       Access: Protected, Static
+//  Description: This is similar to make_from_bam(), but it is
+//               designed for loading the RecorderBase object from the
+//               session log created by a RecorderController.
+////////////////////////////////////////////////////////////////////
+RecorderBase *MouseRecorder::
+make_recorder(const FactoryParams &params) {
+  MouseRecorder *node = new MouseRecorder("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin_recorder(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::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 MouseRecorder.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  DataNode::fillin(scan, manager);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseRecorder::fillin_recorder
+//       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 MouseRecorder.
+////////////////////////////////////////////////////////////////////
+void MouseRecorder::
+fillin_recorder(DatagramIterator &scan, BamReader *manager) {
+  RecorderBase::fillin_recorder(scan, manager);
+  DataNode::fillin_recorder(scan, manager);
+}

+ 112 - 0
panda/src/recorder/mouseRecorder.h

@@ -0,0 +1,112 @@
+// Filename: mouseRecorder.h
+// Created by:  drose (25Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef MOUSERECORDER_H
+#define MOUSERECORDER_H
+
+#include "recorderBase.h"
+#include "dataNode.h"
+#include "dataNodeTransmit.h"
+#include "linmath_events.h"
+#include "buttonEventList.h"
+
+class FactoryParams;
+class BamReader;
+class BamWriter;
+
+////////////////////////////////////////////////////////////////////
+//       Class : MouseRecorder
+// Description : This object records any data generated by a
+//               particular MouseAndKeyboard node on the datagraph for
+//               a session for eventual playback via a
+//               DataGraphPlayback (and a PlaybackController).  To use
+//               it, make it a child of the node you wish to record.
+//               It also serves as a pass-through, so that additional
+//               child nodes may be parented directly to it.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA MouseRecorder : public DataNode, public RecorderBase {
+PUBLISHED:
+  MouseRecorder(const string &name);
+  virtual ~MouseRecorder();
+
+public:
+  virtual void record_frame(BamWriter *manager, Datagram &dg);
+  virtual void play_frame(DatagramIterator &scan, BamReader *manager);
+
+public:
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+protected:
+  // Inherited from DataNode
+  virtual void do_transmit_data(const DataNodeTransmit &input,
+                                DataNodeTransmit &output);
+
+private:
+  // inputs
+  int _pixel_xy_input;
+  int _xy_input;
+  int _button_events_input;
+
+  // outputs
+  int _pixel_xy_output;
+  int _xy_output;
+  int _button_events_output;
+
+  bool _has_mouse;
+  LPoint2f _mouse_xy;
+  LPoint2f _mouse_pixel_xy;
+  PT(ButtonEventList) _live_button_events;
+  PT(ButtonEventList) _save_button_events;
+
+  PT(EventStoreVec2) _pixel_xy;
+  PT(EventStoreVec2) _xy;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void write_recorder(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  static RecorderBase *make_recorder(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+  void fillin_recorder(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    DataNode::init_type();
+    RecorderBase::init_type();
+    register_type(_type_handle, "MouseRecorder",
+                  DataNode::get_class_type(),
+                  RecorderBase::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;
+};
+
+#endif
+

+ 43 - 0
panda/src/recorder/recorderBase.I

@@ -0,0 +1,43 @@
+// Filename: recorderBase.I
+// Created by:  drose (24Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::is_recording
+//       Access: Published
+//  Description: Returns true if this recorder is presently recording
+//               data for saving to a session file, false otherwise.
+//               If this is true, record_data() will be called from
+//               time to time.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderBase::
+is_recording() const {
+  return (_flags & F_recording) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::is_playing
+//       Access: Published
+//  Description: Returns true if this recorder is presently playing back
+//               data from session file, false otherwise.  If this is
+//               true, play_data() will be called from time to time.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderBase::
+is_playing() const {
+  return (_flags & F_playing) != 0;
+}

+ 89 - 0
panda/src/recorder/recorderBase.cxx

@@ -0,0 +1,89 @@
+// Filename: recorderBase.cxx
+// Created by:  drose (24Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "recorderBase.h"
+
+TypeHandle RecorderBase::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::Constructor
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+RecorderBase::
+RecorderBase() {
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+RecorderBase::
+~RecorderBase() {
+  nassertv(_flags == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::record_frame
+//       Access: Public, Virtual
+//  Description: Records the most recent data collected into the
+//               indicated datagram.
+////////////////////////////////////////////////////////////////////
+void RecorderBase::
+record_frame(BamWriter *, Datagram &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::play_frame
+//       Access: Public, Virtual
+//  Description: Reloads the most recent data collected from the
+//               indicated datagram.
+////////////////////////////////////////////////////////////////////
+void RecorderBase::
+play_frame(DatagramIterator &scan, BamReader *manager) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::write_recorder
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for encoding in the session file.  This is very
+//               similar to write_datagram() for TypedWritable
+//               objects, but it is used specifically to write the
+//               Recorder object when generating the session file.  In
+//               many cases, it will be the same as write_datagram().
+//
+//               This balances with fillin_recorder().
+////////////////////////////////////////////////////////////////////
+void RecorderBase::
+write_recorder(BamWriter *, Datagram &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderBase::fillin_recorder
+//       Access: Protected
+//  Description: This internal function is called by make_recorder (in
+//               derived classes) to read in all of the relevant data
+//               from the session file.  It balances with
+//               write_recorder().
+////////////////////////////////////////////////////////////////////
+void RecorderBase::
+fillin_recorder(DatagramIterator &, BamReader *) {
+}

+ 102 - 0
panda/src/recorder/recorderBase.h

@@ -0,0 +1,102 @@
+// Filename: recorderBase.h
+// Created by:  drose (25Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef RECORDERBASE_H
+#define RECORDERBASE_H
+
+#include "pandabase.h"
+#include "referenceCount.h"
+
+class BamReader;
+class BamWriter;
+class Datagram;
+class DatagramIterator;
+class TypedWritable;
+
+////////////////////////////////////////////////////////////////////
+//       Class : RecorderBase
+// Description : This is the base class to a number of objects that
+//               record particular kinds of user input (like a
+//               MouseRecorder) to use in conjunction with a
+//               RecorderController to record the user's inputs
+//               for a session.
+//
+//               Note that RecorderBase does not actually inherit from
+//               TypedObject, even though it defines get_type().  The
+//               assumption is that the classes that derive from
+//               RecorderBase might also inherit independently from
+//               TypedObject.
+//
+//               It also does not inherit from TypedWritable, but it
+//               defines a method called write_recorder() which is
+//               very similar to a TypedWritable's write_datagram().
+//               Classes that derive from RecorderBase and also
+//               inherit from TypedWritable may choose to remap
+//               write_recorder() to do exactly the same thing as
+//               write_datagram(), or they may choose to write
+//               something slightly different.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA RecorderBase : virtual public ReferenceCount {
+protected:
+  RecorderBase();
+
+PUBLISHED:
+  virtual ~RecorderBase();
+
+  INLINE bool is_recording() const;
+  INLINE bool is_playing() const;
+
+public:
+  virtual void record_frame(BamWriter *manager, Datagram &dg);
+  virtual void play_frame(DatagramIterator &scan, BamReader *manager);
+
+  virtual void write_recorder(BamWriter *manager, Datagram &dg);
+
+protected:
+  void fillin_recorder(DatagramIterator &scan, BamReader *manager);
+
+private:
+  enum Flags {
+    F_recording = 0x0001,
+    F_playing   = 0x0002,
+  };
+  short _flags;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ReferenceCount::init_type();
+    register_type(_type_handle, "RecorderBase",
+                  ReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class RecorderController;
+};
+
+#include "recorderBase.I"
+
+#endif
+

+ 294 - 0
panda/src/recorder/recorderController.I

@@ -0,0 +1,294 @@
+// Filename: recorderController.I
+// Created by:  drose (24Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_start_time
+//       Access: Published
+//  Description: Returns the time (and date) at which the current
+//               session was originally recorded (or, in recording
+//               mode, the time at which the current session began).
+////////////////////////////////////////////////////////////////////
+INLINE time_t RecorderController::
+get_start_time() const {
+  return _header._start_time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::set_random_seed
+//       Access: Published
+//  Description: Indicates an arbitrary number to be recorded in the
+//               session file as a random seed, should the application
+//               wish to take advantage of it.  This must be set
+//               before begin_record() is called.
+////////////////////////////////////////////////////////////////////
+INLINE void RecorderController::
+set_random_seed(int random_seed) {
+  _header._random_seed = random_seed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_random_seed
+//       Access: Published
+//  Description: Returns the random seed that was set by a previous
+//               call to set_random_seed(), or the number read from
+//               the session file after begin_playback() has been
+//               called.
+////////////////////////////////////////////////////////////////////
+INLINE int RecorderController::
+get_random_seed() const {
+  return _header._random_seed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::is_recording
+//       Access: Published
+//  Description: Returns true if the controller has been opened for
+//               output, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+is_recording() const {
+  return (_writer != (BamWriter *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::is_playing
+//       Access: Published
+//  Description: Returns true if the controller has been opened for
+//               input, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+is_playing() const {
+  return (_reader != (BamReader *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::is_open
+//       Access: Published
+//  Description: Returns true if the controller has been opened for
+//               either input or output, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+is_open() const {
+  return is_recording() || is_playing();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_filename
+//       Access: Published
+//  Description: Returns the filename that was passed to the most
+//               recent call to begin_record() or begin_playback().
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &RecorderController::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::is_error
+//       Access: Published
+//  Description: Returns true if the controller has been opened for
+//               input or output output and there is an error on the
+//               stream, or false if the controller is closed or if
+//               there is no problem.
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+is_error() {
+  return _dout.is_error() || _din.is_error();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_clock_offset
+//       Access: Published
+//  Description: Returns the delta offset between the actual frame
+//               time and the frame time written to the log.  This is
+//               essentially the time at which the recording (or
+//               playback) started.
+////////////////////////////////////////////////////////////////////
+INLINE double RecorderController::
+get_clock_offset() const {
+  return _clock_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_frame_offset
+//       Access: Published
+//  Description: Returns the delta offset between the actual frame
+//               count and the frame count written to the log.  This is
+//               essentially the frame number at which the recording
+//               (or playback) started.
+////////////////////////////////////////////////////////////////////
+INLINE int RecorderController::
+get_frame_offset() const {
+  return _frame_offset;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::add_recorder
+//       Access: Published
+//  Description: Adds the named recorder to the set of recorders that
+//               are in use.
+//
+//               If the controller is in recording mode, the named
+//               recorder will begin recording its status to the
+//               session file.  If the controller is in playback mode
+//               and the name and type matches a recorder in the
+//               session file, the recorder will begin receiving data.
+////////////////////////////////////////////////////////////////////
+INLINE void RecorderController::
+add_recorder(const string &name, RecorderBase *recorder) {
+  _user_table->add_recorder(name, recorder);
+  _user_table_modified = true;
+
+  // We can only add the state flag immediately if we are in recording
+  // mode.  In playback mode, we're not sure yet whether the new
+  // recorder state will actually be playing (we won't know until we
+  // merge the tables in play_frame()).
+  if (is_recording()) {
+    recorder->_flags |= RecorderBase::F_recording;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::has_recorder
+//       Access: Published
+//  Description: Returns true if the named recorder has been added to
+//               the table by a previous call to add_recorder(), false
+//               otherwise. 
+//
+//               If the controller is in playback mode, this will also
+//               return false for a recorder that was found in the
+//               session file but was never explicitly added via
+//               add_recorder(); see get_recorder().
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+has_recorder(const string &name) const {
+  return (_user_table->get_recorder(name) != (RecorderBase *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_recorder
+//       Access: Published
+//  Description: Returns the recorder with the indicated name, or NULL
+//               if there is no such recorder.
+//
+//               If the controller is in playback mode, this may
+//               return the recorder matching the indicated name as
+//               read from the session file, even if it was never
+//               added to the table by the user.  In this case,
+//               has_recorder() may return false, but get_recorder()
+//               will return a non-NULL value.
+////////////////////////////////////////////////////////////////////
+INLINE RecorderBase *RecorderController::
+get_recorder(const string &name) const {
+  RecorderBase *recorder = _user_table->get_recorder(name);
+  if (is_playing() && recorder == (RecorderBase *)NULL) {
+    recorder = _active_table->get_recorder(name);
+  }
+  return recorder;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::remove_recorder
+//       Access: Published
+//  Description: Removes the named recorder from the table.  Returns
+//               true if successful, false if there was no such
+//               recorder.
+//
+//               If the controller is in recording mode, the named
+//               recorder will stop recording.  If the controller is
+//               in playback mode, the named recorder will
+//               disassociate itself from the session file (but if the
+//               session file still has data for this name, a default
+//               recorder will take its place to decode the data from
+//               the session file).
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+remove_recorder(const string &name) {
+  // If we are playing or recording, immediately remove the state flag
+  // from the recorder.  (When we are playing, the state flag will get
+  // removed automatically at the next call to play_frame(), but we
+  // might as well be aggressive and remove it now.  When we are
+  // recording, we have to remove it now.)
+  if (is_recording() || is_playing()) {
+    RecorderBase *recorder = _user_table->get_recorder(name);
+    if (recorder != (RecorderBase *)NULL) {
+      recorder->_flags &= ~(RecorderBase::F_recording | RecorderBase::F_playing);
+    }
+  }
+  _user_table_modified = true;
+  return _user_table->remove_recorder(name);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::set_frame_tie
+//       Access: Published
+//  Description: Sets the frame_tie flag.
+//
+//               When this is true, sessions are played back
+//               frame-for-frame, based on the frame count of the
+//               recorded session.  This gives the most accurate
+//               playback, but the playback rate will vary according
+//               to the frame rate of the playback machine.
+//
+//               When this is false, sessions are played back at real
+//               time, based on the clock of the recorded session.
+//               This may introduce playback discrepencies if the
+//               frames do not fall at exactly the same times as they
+//               did in the original.
+////////////////////////////////////////////////////////////////////
+INLINE void RecorderController::
+set_frame_tie(bool frame_tie) {
+  _frame_tie = frame_tie;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_frame_tie
+//       Access: Published
+//  Description: See set_frame_tie().
+////////////////////////////////////////////////////////////////////
+INLINE bool RecorderController::
+get_frame_tie() const {
+  return _frame_tie;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::get_factory
+//       Access: Public, Static
+//  Description: Returns the global RecorderFactory for generating
+//               TypedWritable objects
+////////////////////////////////////////////////////////////////////
+INLINE RecorderController::RecorderFactory *RecorderController::
+get_factory() {
+  if (_factory == (RecorderFactory *)NULL) {
+    create_factory();
+  }
+  return _factory;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::create_factory
+//       Access: Private, Static
+//  Description: Creates a new RecorderFactory for generating
+//               TypedWritable objects
+////////////////////////////////////////////////////////////////////
+INLINE void RecorderController::
+create_factory() {
+  _factory = new RecorderFactory;
+}

+ 370 - 0
panda/src/recorder/recorderController.cxx

@@ -0,0 +1,370 @@
+// Filename: recorderController.cxx
+// Created by:  drose (24Jan04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "recorderController.h"
+#include "recorderFrame.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "config_recorder.h"
+#include "bam.h"
+
+TypeHandle RecorderController::_type_handle;
+RecorderController::RecorderFactory *RecorderController::_factory = NULL;
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+RecorderController::
+RecorderController() {
+  _clock_offset = 0.0;
+  _frame_offset = 0;
+  _writer = (BamWriter *)NULL;
+  _reader = (BamReader *)NULL;
+  _frame_tie = true;
+  _user_table = new RecorderTable;
+  _user_table_modified = false;
+  _file_table = NULL;
+  _active_table = NULL;
+  _eof = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::Destructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+RecorderController::
+~RecorderController() {
+  close();
+  delete _user_table;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::begin_record
+//       Access: Published
+//  Description: Begins recording data to the indicated filename.  All
+//               of the recorders in use should already have been
+//               added.
+////////////////////////////////////////////////////////////////////
+bool RecorderController::
+begin_record(const Filename &filename) {
+  close();
+  _filename = filename;
+  ClockObject *global_clock = ClockObject::get_global_clock();
+  _clock_offset = global_clock->get_frame_time();
+  _frame_offset = global_clock->get_frame_count();
+
+  time(&_header._start_time);
+
+  if (!_dout.open(_filename)) {
+    recorder_cat.error() << "Unable to open " << _filename << "\n";
+    return false;
+  }
+
+  if (!_dout.write_header(_bam_header)) {
+    recorder_cat.error() << "Unable to write to " << _filename << "\n";
+    return false;
+  }
+
+  _writer = new BamWriter(&_dout);
+
+  if (!_writer->init()) {
+    close();
+    return false;
+  }
+
+  // Write out the header information.
+  _writer->write_object(&_header);
+
+  _user_table_modified = true;
+
+  // Tell all of our recorders that they're live now.
+  RecorderTable::Recorders::iterator ri;
+  for (ri = _user_table->_recorders.begin(); 
+       ri != _user_table->_recorders.end(); 
+       ++ri) {
+    RecorderBase *recorder = (*ri).second;
+    recorder->_flags |= RecorderBase::F_recording;
+  }
+
+  recorder_cat.info() 
+    << "Recording session to " << _filename << "\n";
+    
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::begin_playback
+//       Access: Published
+//  Description: Begins playing back data from the indicated filename.
+//               All of the recorders in use should already have been
+//               added, although this may define additional recorders
+//               if they are present in the file (these new recorders
+//               will not be used).  This may also undefine recorders
+//               that were previously added but are not present in the
+//               file.
+////////////////////////////////////////////////////////////////////
+bool RecorderController::
+begin_playback(const Filename &filename) {
+  close();
+  _filename = filename;
+  ClockObject *global_clock = ClockObject::get_global_clock();
+  _clock_offset = global_clock->get_frame_time();
+  _frame_offset = global_clock->get_frame_count();
+
+  if (!_din.open(_filename)) {
+    recorder_cat.error() << "Unable to open " << _filename << "\n";
+    return false;
+  }
+
+  string head;
+  if (!_din.read_header(head, _bam_header.size()) || head != _bam_header) {
+    recorder_cat.error() << "Unable to read " << _filename << "\n";
+    return false;
+  }
+
+  _reader = new BamReader(&_din);
+  if (!_reader->init()) {
+    close();
+    return false;
+  }
+
+  _user_table_modified = true;
+  _active_table = new RecorderTable;
+  _eof = false;
+
+  // Start out by reading the RecorderHeader.
+  TypedWritable *object = _reader->read_object();
+
+  if (object == (TypedWritable *)NULL || 
+      !object->is_of_type(RecorderHeader::get_class_type())) {
+    recorder_cat.error()
+      << _filename << " does not contain a recorded session.\n";
+    close();
+    return false;
+  }
+
+  if (!_reader->resolve()) {
+    recorder_cat.warning()
+      << "Unable to resolve header data.\n";
+  }
+
+  RecorderHeader *new_header = DCAST(RecorderHeader, object);
+  _header = (*new_header);
+  delete new_header;
+
+  // Now read the first frame.
+  _next_frame = read_frame();
+  if (_next_frame == (RecorderFrame *)NULL) {
+    recorder_cat.error()
+      << _filename << " does not contain any frames.\n";
+    close();
+    return false;
+  }
+
+  recorder_cat.info() 
+    << "Playing back session from " << _filename << "\n";
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::close
+//       Access: Published
+//  Description: Finishes recording data to the indicated filename.
+////////////////////////////////////////////////////////////////////
+void RecorderController::
+close() {
+  if (_writer != (BamWriter *)NULL) {
+    delete _writer;
+    _writer = NULL;
+
+    // Tell all of our recorders that they're no longer recording.
+    RecorderTable::Recorders::iterator ri;
+    for (ri = _user_table->_recorders.begin(); 
+         ri != _user_table->_recorders.end(); 
+         ++ri) {
+      RecorderBase *recorder = (*ri).second;
+      recorder->_flags &= ~RecorderBase::F_recording;
+    }
+  }
+  if (_reader != (BamReader *)NULL) {
+    delete _reader;
+    _reader = NULL;
+
+    // Tell all of our recorders that they're no longer playing.
+    RecorderTable::Recorders::iterator ri;
+    for (ri = _active_table->_recorders.begin(); 
+         ri != _active_table->_recorders.end(); 
+         ++ri) {
+      RecorderBase *recorder = (*ri).second;
+      recorder->_flags &= ~RecorderBase::F_playing;
+    }
+  }
+  _dout.close();
+  _din.close();
+
+  if (_file_table != (RecorderTable *)NULL) {
+    delete _file_table;
+    _file_table = (RecorderTable *)NULL;
+  }
+
+  if (_active_table != (RecorderTable *)NULL) {
+    delete _active_table;
+    _active_table = (RecorderTable *)NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::record_frame
+//       Access: Published
+//  Description: Gets the next frame of data from all of the active
+//               recorders and adds it to the output file.
+////////////////////////////////////////////////////////////////////
+void RecorderController::
+record_frame() {
+  if (is_recording()) {
+    ClockObject *global_clock = ClockObject::get_global_clock();
+    double now = global_clock->get_frame_time() - _clock_offset;
+    int frame = global_clock->get_frame_count() - _frame_offset;
+
+    RecorderFrame data(now, frame, _user_table_modified, _user_table);
+    _user_table_modified = false;
+    
+    _writer->write_object(&data);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::play_frame
+//       Access: Published
+//  Description: Gets the next frame of data from all of the active
+//               recorders and adds it to the output file.
+////////////////////////////////////////////////////////////////////
+void RecorderController::
+play_frame() {
+  if (is_playing()) {
+    if (_eof) {
+      close();
+      return;
+    }
+
+    ClockObject *global_clock = ClockObject::get_global_clock();
+    double now = global_clock->get_frame_time() - _clock_offset;
+    int frame = global_clock->get_frame_count() - _frame_offset;
+
+    while (_next_frame != (RecorderFrame *)NULL) {
+      if (_frame_tie) {
+        if (frame < _next_frame->_frame) {
+          // We haven't reached the next frame yet.
+          return;
+        }
+
+        // Insist that the clock runs at the same rate as it did in
+        // the previous session.
+        //global_clock->set_frame_time(_next_frame->_timestamp + _clock_offset);
+        //global_clock->set_real_time(_next_frame->_timestamp + _clock_offset);
+
+        // Hmm, that's crummy.  Just keep the clock offset up-to-date.
+        _clock_offset = global_clock->get_frame_time() - _next_frame->_timestamp;
+
+      } else {
+        if (now < _next_frame->_timestamp) {
+          // We haven't reached the next frame yet.
+          return;
+        }
+
+        // Keep our frame_offset up-to-date.
+        _frame_offset = global_clock->get_frame_count() - _next_frame->_frame;
+      }
+
+      if (_next_frame->_table_changed && _file_table != _next_frame->_table) {
+        delete _file_table;
+        _file_table = _next_frame->_table;
+      }
+
+      if (_next_frame->_table_changed || _user_table_modified) {
+        // We're about to change the active table.  Temporarily
+        // disable the playing flag on the currently-active recorders.
+        RecorderTable::Recorders::iterator ri;
+        for (ri = _active_table->_recorders.begin(); 
+             ri != _active_table->_recorders.end(); 
+             ++ri) {
+          RecorderBase *recorder = (*ri).second;
+          recorder->_flags &= ~RecorderBase::F_playing;
+        }
+
+        delete _active_table;
+        _active_table = new RecorderTable(*_file_table);
+        _active_table->merge_from(*_user_table);
+        _user_table_modified = false;
+        
+        // Now reenable the playing flag on the newly-active
+        // recorders.
+        for (ri = _active_table->_recorders.begin(); 
+             ri != _active_table->_recorders.end(); 
+             ++ri) {
+          RecorderBase *recorder = (*ri).second;
+          recorder->_flags |= RecorderBase::F_playing;
+        }
+      }
+
+      _next_frame->_table = _active_table;
+      _next_frame->play_frame(_reader);
+
+      delete _next_frame;
+      _next_frame = read_frame();
+    }
+
+    if (_reader->is_eof()) {
+      recorder_cat.info()
+        << "End of recorded session.\n";
+    } else {
+      recorder_cat.error()
+        << "Unable to read datagram from recorded session.\n";
+    }
+    _eof = true;
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: RecorderController::read_frame
+//       Access: Private
+//  Description: Loads the next frame data from the playback session
+//               file.  Returns the frame data pointer on success, or
+//               NULL on failure.
+////////////////////////////////////////////////////////////////////
+RecorderFrame *RecorderController::
+read_frame() {
+  TypedWritable *object = _reader->read_object();
+
+  if (object == (TypedWritable *)NULL || 
+      !object->is_of_type(RecorderFrame::get_class_type())) {
+    return NULL;
+  }
+
+  if (!_reader->resolve()) {
+    recorder_cat.warning()
+      << "Unable to resolve frame data.\n";
+  }
+
+  return DCAST(RecorderFrame, object);
+}

Some files were not shown because too many files changed in this diff