Panda3D
cConnectionRepository.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file cConnectionRepository.cxx
10  * @author drose
11  * @date 2004-05-17
12  */
13 
14 #include "cConnectionRepository.h"
15 #include "dcmsgtypes.h"
16 #include "dcClass.h"
17 #include "dcPacker.h"
18 
19 #include "config_distributed.h"
20 #include "config_downloader.h"
21 #include "httpChannel.h"
22 #include "urlSpec.h"
23 #include "datagramIterator.h"
24 #include "throw_event.h"
25 #include "pStatTimer.h"
26 
27 #ifdef HAVE_PYTHON
28 #include "py_panda.h"
29 #endif
30 
31 using std::endl;
32 using std::string;
33 
34 const string CConnectionRepository::_overflow_event_name = "CRDatagramOverflow";
35 
36 #ifndef CPPPARSER
37 PStatCollector CConnectionRepository::_update_pcollector("App:Show code:readerPollTask:Update");
38 #endif // CPPPARSER
39 
40 /**
41  *
42  */
43 CConnectionRepository::
44 CConnectionRepository(bool has_owner_view, bool threaded_net) :
45  _lock("CConnectionRepository::_lock"),
46 #ifdef HAVE_PYTHON
47  _python_repository(nullptr),
48 #endif
49 #ifdef HAVE_OPENSSL
50  _http_conn(nullptr),
51 #endif
52 #ifdef HAVE_NET
53  _cw(&_qcm, threaded_net ? 1 : 0),
54  _qcr(&_qcm, threaded_net ? 1 : 0),
55 #endif
56 #ifdef WANT_NATIVE_NET
57  _bdc(4096000,4096000,1400),
58  _native(false),
59 #endif
60  _client_datagram(true),
61  _handle_datagrams_internally(handle_datagrams_internally),
62  _simulated_disconnect(false),
63  _verbose(distributed_cat.is_spam()),
64  _time_warning(0.0),
65 // _msg_channels(),
66  _msg_sender(0),
67  _msg_type(0),
68  _has_owner_view(has_owner_view),
69  _handle_c_updates(true),
70  _want_message_bundling(true),
71  _bundling_msgs(0),
72  _in_quiet_zone(0)
73 {
74 #if defined(HAVE_NET) && defined(SIMULATE_NETWORK_DELAY)
75  if (min_lag != 0.0 || max_lag != 0.0) {
76  _qcr.start_delay(min_lag, max_lag);
77  }
78 #endif
79  _tcp_header_size = tcp_header_size;
80 }
81 
82 /**
83  *
84  */
85 CConnectionRepository::
86 ~CConnectionRepository() {
87  disconnect();
88 }
89 
90 /**
91  * Sets the header size of TCP packets. At the present, legal values for this
92  * are 0, 2, or 4; this specifies the number of bytes to use encode the
93  * datagram length at the start of each TCP datagram. Sender and receiver
94  * must independently agree on this.
95  */
97 set_tcp_header_size(int tcp_header_size) {
98  _tcp_header_size = tcp_header_size;
99 
100 #ifdef HAVE_OPENSSL
101  if (_http_conn != nullptr) {
102  _http_conn->set_tcp_header_size(tcp_header_size);
103  }
104 #endif
105 
106 #ifdef HAVE_NET
107  _cw.set_tcp_header_size(tcp_header_size);
108  _qcr.set_tcp_header_size(tcp_header_size);
109 #endif
110 }
111 
112 #ifdef HAVE_OPENSSL
113 /**
114  * Once a connection has been established via the HTTP interface, gets the
115  * connection and uses it. The supplied HTTPChannel object must have a
116  * connection available via get_connection().
117  */
118 void CConnectionRepository::
119 set_connection_http(HTTPChannel *channel) {
120  ReMutexHolder holder(_lock);
121 
122  disconnect();
123  nassertv(channel->is_connection_ready());
124  _http_conn = channel->get_connection();
125  _http_conn->set_tcp_header_size(_tcp_header_size);
126 #ifdef SIMULATE_NETWORK_DELAY
127  if (min_lag != 0.0 || max_lag != 0.0) {
128  _http_conn->start_delay(min_lag, max_lag);
129  }
130 #endif
131 }
132 #endif // HAVE_OPENSSL
133 
134 #ifdef HAVE_OPENSSL
135 /**
136  * Returns the SocketStream that internally represents the already-established
137  * HTTP connection. Returns NULL if there is no current HTTP connection.
138  */
139 SocketStream *CConnectionRepository::
140 get_stream() {
141  ReMutexHolder holder(_lock);
142 
143  return _http_conn;
144 }
145 #endif // HAVE_OPENSSL
146 
147 
148 #ifdef HAVE_NET
149 /**
150  * Uses Panda's "net" library to try to connect to the server and port named
151  * in the indicated URL. Returns true if successful, false otherwise.
152  */
153 bool CConnectionRepository::
154 try_connect_net(const URLSpec &url) {
155  ReMutexHolder holder(_lock);
156 
157  disconnect();
158 
159  _net_conn =
160  _qcm.open_TCP_client_connection(url.get_server(), url.get_port(),
161  game_server_timeout_ms);
162 
163  if (_net_conn != nullptr) {
164  _net_conn->set_no_delay(true);
165  _qcr.add_connection(_net_conn);
166  return true;
167  }
168 
169  return false;
170 }
171 #endif // HAVE_NET
172 
173 #ifdef WANT_NATIVE_NET
174 /**
175  * Connects to the server using Panda's low-level and fast "native net"
176  * library.
177  */
178 bool CConnectionRepository::
179 connect_native(const URLSpec &url) {
180  ReMutexHolder holder(_lock);
181 
182  _native=true;
183  Socket_Address addr;
184  addr.set_host(url.get_server(),url.get_port());
185  _bdc.ClearAddresses();
186  _bdc.AddAddress(addr);
187  return _bdc.DoConnect();
188 }
189 
190 #endif //WANT NATIVE NET
191 
192 #ifdef SIMULATE_NETWORK_DELAY
193 /**
194  * Enables a simulated network latency. All datagrams received from this
195  * point on will be held for a random interval of least min_delay seconds, and
196  * no more than max_delay seconds, before being visible. It is as if
197  * datagrams suddenly took much longer to arrive.
198  *
199  * This should *only* be called if the underlying socket is non-blocking. If
200  * you call this on a blocking socket, it will force all datagrams to be held
201  * up until the socket closes.
202  *
203  * This has no effect if the connection method is via the "native net"
204  * library.
205  */
206 void CConnectionRepository::
207 start_delay(double min_delay, double max_delay) {
208  ReMutexHolder holder(_lock);
209 
210  if (min_delay != 0.0 || max_delay != 0.0) {
211 #ifdef HAVE_NET
212  _qcr.start_delay(min_delay, max_delay);
213 #endif // HAVE_NET
214 #ifdef HAVE_OPENSSL
215  if (_http_conn != nullptr) {
216  _http_conn->start_delay(min_delay, max_delay);
217  }
218 #endif // HAVE_OPENSSL
219  } else {
220  stop_delay();
221  }
222 }
223 #endif // SIMULATE_NETWORK_DELAY
224 
225 #ifdef SIMULATE_NETWORK_DELAY
226 /**
227  * Disables the simulated network latency started by a previous call to
228  * start_delay(). Datagrams will once again be visible as soon as they are
229  * received.
230  */
231 void CConnectionRepository::
232 stop_delay() {
233  ReMutexHolder holder(_lock);
234 
235 #ifdef HAVE_NET
236  _qcr.stop_delay();
237 #endif // HAVE_NET
238 #ifdef HAVE_OPENSSL
239  if (_http_conn != nullptr) {
240  _http_conn->stop_delay();
241  }
242 #endif // HAVE_OPENSSL
243 }
244 #endif // SIMULATE_NETWORK_DELAY
245 
246 /**
247  * Returns true if a new datagram is available, false otherwise. If the
248  * return value is true, the new datagram may be retrieved via get_datagram(),
249  * or preferably, with get_datagram_iterator() and get_msg_type().
250  */
253  ReMutexHolder holder(_lock);
254 
255  if (_simulated_disconnect) {
256  return false;
257  }
258  #ifdef WANT_NATIVE_NET
259  if(_native)
260  _bdc.Flush();
261  #endif //WANT_NATIVE_NET
262 
263  while (do_check_datagram()) {
264  if (get_verbose()) {
265  describe_message(nout, "RECV", _dg);
266  }
267 
268  // Start breaking apart the datagram.
269  _di = DatagramIterator(_dg);
270 
271  if (!_client_datagram) {
272  unsigned char wc_cnt;
273  wc_cnt = _di.get_uint8();
274  _msg_channels.clear();
275  for (unsigned char lp1 = 0; lp1 < wc_cnt; lp1++) {
276  CHANNEL_TYPE schan = _di.get_uint64();
277  _msg_channels.push_back(schan);
278  }
279  _msg_sender = _di.get_uint64();
280 
281 #ifdef HAVE_PYTHON
282  // For now, we need to stuff this field onto the Python structure, to
283  // support legacy code that expects to find it there.
284  if (_python_repository != nullptr) {
285 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
286  PyGILState_STATE gstate;
287  gstate = PyGILState_Ensure();
288 #endif
289  PyObject *value = PyLong_FromUnsignedLongLong(_msg_sender);
290  PyObject_SetAttrString(_python_repository, "msgSender", value);
291  Py_DECREF(value);
292 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
293  PyGILState_Release(gstate);
294 #endif
295  }
296 #endif // HAVE_PYTHON
297  }
298 
299  _msg_type = _di.get_uint16();
300  // Is this a message that we can process directly?
301  if (!_handle_datagrams_internally) {
302  return true;
303  }
304 
305  switch (_msg_type) {
306 #ifdef HAVE_PYTHON
307  case CLIENT_OBJECT_SET_FIELD:
308  case STATESERVER_OBJECT_SET_FIELD:
309  if (_handle_c_updates) {
310  if (_has_owner_view) {
311  if (!handle_update_field_owner()) {
312  return false;
313  }
314  } else {
315  if (!handle_update_field()) {
316  return false;
317  }
318  }
319  } else {
320  // Let the caller (Python) deal with this update.
321  return true;
322  }
323  break;
324 #endif // HAVE_PYTHON
325 
326  default:
327  // Some unknown message; let the caller deal with it.
328  return true;
329  }
330  }
331 
332  // No datagrams available.
333  return false;
334 }
335 
336 /**
337  * Returns true if the connection to the gameserver is established and still
338  * good, false if we are not connected. A false value means either (a) we
339  * never successfully connected, (b) we explicitly called disconnect(), or (c)
340  * we were connected, but the connection was spontaneously lost.
341  */
344  ReMutexHolder holder(_lock);
345 
346 #ifdef WANT_NATIVE_NET
347  if(_native)
348  return (_bdc.IsConnected());
349 #endif
350 
351 #ifdef HAVE_NET
352  if (_net_conn) {
353  if (_qcm.reset_connection_available()) {
354  PT(Connection) reset_connection;
355  if (_qcm.get_reset_connection(reset_connection)) {
356  _qcm.close_connection(reset_connection);
357  if (reset_connection == _net_conn) {
358  // Whoops, lost our connection.
359  _net_conn = nullptr;
360  return false;
361  }
362  }
363  }
364  return true;
365  }
366 #endif // HAVE_NET
367 
368 #ifdef HAVE_OPENSSL
369  if (_http_conn) {
370  if (!_http_conn->is_closed()) {
371  return true;
372  }
373 
374  // Connection lost.
375  delete _http_conn;
376  _http_conn = nullptr;
377  }
378 #endif // HAVE_OPENSSL
379 
380  return false;
381 }
382 
383 /**
384  * Queues the indicated datagram for sending to the server. It may not get
385  * sent immediately if collect_tcp is in effect; call flush() to guarantee it
386  * is sent now.
387  */
390  ReMutexHolder holder(_lock);
391 
392  if (_simulated_disconnect) {
393  distributed_cat.warning()
394  << "Unable to send datagram during simulated disconnect.\n";
395  return false;
396  }
397 
398  if (get_verbose()) {
399  describe_message(nout, "SEND", dg);
400  }
401 
403  bundle_msg(dg);
404  return true;
405  }
406 
407 #ifdef WANT_NATIVE_NET
408  if (_native) {
409  bool result = _bdc.SendMessage(dg);
410  if (!result && _bdc.IsConnected()) {
411 #ifdef HAVE_PYTHON
412  std::ostringstream s;
413 
414 #if PY_VERSION_HEX >= 0x03030000
415  PyObject *exc_type = PyExc_ConnectionError;
416 #else
417  PyObject *exc_type = PyExc_OSError;
418 #endif
419 
420  s << endl << "Error sending message: " << endl;
421  dg.dump_hex(s);
422  s << "Message data: " << dg.get_data() << endl;
423 
424  string message = s.str();
425  PyErr_SetString(exc_type, message.c_str());
426 #endif
427  }
428  return result;
429  }
430 #endif
431 
432 #ifdef HAVE_NET
433  if (_net_conn) {
434  _cw.send(dg, _net_conn);
435  return true;
436  }
437 #endif // HAVE_NET
438 
439 #ifdef HAVE_OPENSSL
440  if (_http_conn) {
441  if (!_http_conn->send_datagram(dg)) {
442  distributed_cat.warning()
443  << "Could not send datagram.\n";
444  return false;
445  }
446 
447  return true;
448  }
449 #endif // HAVE_OPENSSL
450 
451  distributed_cat.warning()
452  << "Unable to send datagram after connection is closed.\n";
453  return false;
454 }
455 
456 /**
457  * Send a set of messages to the state server that will be processed
458  * atomically. For instance, you can do a combined setLocation/setPos and
459  * prevent race conditions where clients briefly get the setLocation but not
460  * the setPos, because the state server hasn't processed the setPos yet
461  */
464  ReMutexHolder holder(_lock);
465 
466  // store up network messages until sendMessageBundle is called all updates
467  // in between must be sent from the same doId (updates must all affect the
468  // same DistributedObject) it is an error to call this again before calling
469  // sendMessageBundle
470  if (get_verbose()) {
471  nout << "CR::SEND:BUNDLE_START(" << _bundling_msgs << ")" << endl;
472  }
473  if (_bundling_msgs == 0) {
474  _bundle_msgs.clear();
475  }
476  ++_bundling_msgs;
477 }
478 
479 /**
480  * Send network messages queued up since startMessageBundle was called.
481  */
483 send_message_bundle(unsigned int channel, unsigned int sender_channel) {
484  ReMutexHolder holder(_lock);
485  nassertv(_bundling_msgs);
486 
487  --_bundling_msgs;
488 
489  if (get_verbose()) {
490  nout << "CR::SEND:BUNDLE_FINISH(" << _bundling_msgs << ")" << endl;
491  }
492 
493  // if _bundling_msgs ref count is zero, send the bundle out
494  if (_bundling_msgs == 0 && get_want_message_bundling()) {
495  Datagram dg;
496  // add server header (see PyDatagram.addServerHeader)
497  dg.add_int8(1);
498  dg.add_uint64(channel);
499  dg.add_uint64(sender_channel);
500  //dg.add_uint16(STATESERVER_BOUNCE_MESSAGE);
501  // add each bundled message
502  BundledMsgVector::const_iterator bmi;
503  for (bmi = _bundle_msgs.begin(); bmi != _bundle_msgs.end(); bmi++) {
504  dg.add_string(*bmi);
505  }
506 
507  send_datagram(dg);
508  }
509 }
510 
511 /**
512  * throw out any msgs that have been queued up for message bundles
513  */
516  ReMutexHolder holder(_lock);
517 
518  nassertv(is_bundling_messages());
519  _bundling_msgs = 0;
520  _bundle_msgs.clear();
521 }
522 
523 /**
524  *
525  */
526 void CConnectionRepository::
527 bundle_msg(const Datagram &dg) {
528  ReMutexHolder holder(_lock);
529 
530  nassertv(is_bundling_messages());
531  _bundle_msgs.push_back(dg.get_message());
532 }
533 
534 /**
535  * Sends the most recently queued data if enough time has elapsed. This only
536  * has meaning if set_collect_tcp() has been set to true.
537  */
540  ReMutexHolder holder(_lock);
541 
542  if (_simulated_disconnect) {
543  return false;
544  }
545 
546 #ifdef WANT_NATIVE_NET
547  if(_native)
548  return true; //Maybe we should just flush here for now?
549 #endif
550 
551 #ifdef HAVE_NET
552  if (_net_conn) {
553  return _net_conn->consider_flush();
554  }
555 #endif // HAVE_NET
556 
557 #ifdef HAVE_OPENSSL
558  if (_http_conn) {
559  return _http_conn->consider_flush();
560  }
561 #endif // HAVE_OPENSSL
562 
563  return false;
564 }
565 
566 /**
567  * Sends the most recently queued data now. This only has meaning if
568  * set_collect_tcp() has been set to true.
569  */
571 flush() {
572  ReMutexHolder holder(_lock);
573 
574  if (_simulated_disconnect) {
575  return false;
576  }
577  #ifdef WANT_NATIVE_NET
578  if(_native)
579  return _bdc.Flush();
580  #endif
581 
582  #ifdef HAVE_NET
583  if (_net_conn) {
584  return _net_conn->flush();
585  }
586  #endif // HAVE_NET
587 
588  #ifdef HAVE_OPENSSL
589  if (_http_conn) {
590  return _http_conn->flush();
591  }
592  #endif // HAVE_OPENSSL
593 
594  return false;
595 }
596 
597 /**
598  * Closes the connection to the server.
599  */
602  ReMutexHolder holder(_lock);
603 
604  #ifdef WANT_NATIVE_NET
605  if(_native) {
606  _bdc.Reset();
607  _bdc.ClearAddresses();
608  }
609  #endif
610  #ifdef HAVE_NET
611  if (_net_conn) {
612  _qcm.close_connection(_net_conn);
613  _net_conn = nullptr;
614  }
615  #endif // HAVE_NET
616 
617  #ifdef HAVE_OPENSSL
618  if (_http_conn) {
619  _http_conn->close();
620  delete _http_conn;
621  _http_conn = nullptr;
622  }
623  #endif // HAVE_OPENSSL
624 
625  _simulated_disconnect = false;
626 }
627 
628 /**
629  * May be called at application shutdown to ensure all threads are cleaned up.
630  */
633  disconnect();
634 
635  #ifdef HAVE_NET
636  _cw.shutdown();
637  _qcr.shutdown();
638  #endif // HAVE_NET
639 }
640 
641 /**
642  * The private implementation of check_datagram(), this gets one datagram if
643  * it is available.
644  */
645 bool CConnectionRepository::
646 do_check_datagram() {
647  #ifdef WANT_NATIVE_NET
648  if(_native) {
649  return _bdc.GetMessage(_dg);
650  }
651  #endif
652  #ifdef HAVE_NET
653  if (_net_conn) {
654  _net_conn->consider_flush();
655  if (_qcr.get_overflow_flag()) {
656  throw_event(get_overflow_event_name());
657  _qcr.reset_overflow_flag();
658  }
659  return (_qcr.data_available() && _qcr.get_data(_dg));
660  }
661  #endif // HAVE_NET
662 
663  #ifdef HAVE_OPENSSL
664  if (_http_conn) {
665  _http_conn->consider_flush();
666  return _http_conn->receive_datagram(_dg);
667  }
668  #endif // HAVE_OPENSSL
669 
670 
671  return false;
672 }
673 
674 /**
675  * Directly handles an update message on a field. Python never touches the
676  * datagram; it just gets its distributed method called with the appropriate
677  * parameters. Returns true if everything is ok, false if there was an error
678  * processing the field's update method.
679  */
680 bool CConnectionRepository::
681 handle_update_field() {
682 #ifdef HAVE_PYTHON
683 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
684  PyGILState_STATE gstate;
685  gstate = PyGILState_Ensure();
686 #endif
687 
688  PStatTimer timer(_update_pcollector);
689  unsigned int do_id = _di.get_uint32();
690  if (_python_repository != nullptr)
691  {
692  PyObject *doId2do =
693  PyObject_GetAttrString(_python_repository, "doId2do");
694  nassertr(doId2do != nullptr, false);
695 
696  #ifdef USE_PYTHON_2_2_OR_EARLIER
697  PyObject *doId = PyInt_FromLong(do_id);
698  #else
699  PyObject *doId = PyLong_FromUnsignedLong(do_id);
700  #endif
701  PyObject *distobj = PyDict_GetItem(doId2do, doId);
702  Py_DECREF(doId);
703  Py_DECREF(doId2do);
704 
705  if (distobj != nullptr) {
706  PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
707  nassertr(dclass_obj != nullptr, false);
708 
709 
710  PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
711  Py_DECREF(dclass_obj);
712  nassertr(dclass_this != nullptr, false);
713 
714  DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
715  Py_DECREF(dclass_this);
716 
717  // If in quiet zone mode, throw update away unless distobj has
718  // 'neverDisable' attribute set to non-zero
719  if (_in_quiet_zone) {
720  PyObject *neverDisable = PyObject_GetAttrString(distobj, "neverDisable");
721  nassertr(neverDisable != nullptr, false);
722 
723  unsigned int cNeverDisable = PyLong_AsLong(neverDisable);
724  if (!cNeverDisable) {
725  // in quiet zone and distobj is disable-able drop update on the
726  // floor
727 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
728  PyGILState_Release(gstate);
729 #endif
730  return true;
731  }
732  }
733 
734  // It's a good idea to ensure the reference count to distobj is raised
735  // while we call the update method--otherwise, the update method might
736  // get into trouble if it tried to delete the object from the doId2do
737  // map.
738  Py_INCREF(distobj);
739  dclass->receive_update(distobj, _di);
740  Py_DECREF(distobj);
741 
742  if (PyErr_Occurred()) {
743 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
744  PyGILState_Release(gstate);
745 #endif
746  return false;
747  }
748  }
749 
750  }
751 
752 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
753  PyGILState_Release(gstate);
754 #endif
755  #endif // HAVE_PYTHON
756  return true;
757 }
758 
759 
760 /**
761  * Directly handles an update message on a field. Supports 'owner' views of
762  * objects, separate from 'visible' view, and forwards fields to the
763  * appropriate view(s) based on DC flags. Python never touches the datagram;
764  * it just gets its distributed method called with the appropriate parameters.
765  * Returns true if everything is ok, false if there was an error processing
766  * the field's update method.
767  */
768 bool CConnectionRepository::
769 handle_update_field_owner() {
770 #ifdef HAVE_PYTHON
771 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
772  PyGILState_STATE gstate;
773  gstate = PyGILState_Ensure();
774 #endif
775 
776  PStatTimer timer(_update_pcollector);
777  unsigned int do_id = _di.get_uint32();
778  if (_python_repository != nullptr) {
779  PyObject *doId2do =
780  PyObject_GetAttrString(_python_repository, "doId2do");
781  nassertr(doId2do != nullptr, false);
782 
783  PyObject *doId2ownerView =
784  PyObject_GetAttrString(_python_repository, "doId2ownerView");
785  nassertr(doId2ownerView != nullptr, false);
786 
787  #ifdef USE_PYTHON_2_2_OR_EARLIER
788  PyObject *doId = PyInt_FromLong(do_id);
789  #else
790  PyObject *doId = PyLong_FromUnsignedLong(do_id);
791  #endif
792 
793  // pass the update to the owner view first
794  PyObject *distobjOV = PyDict_GetItem(doId2ownerView, doId);
795  Py_DECREF(doId2ownerView);
796 
797  if (distobjOV != nullptr) {
798  PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
799  nassertr(dclass_obj != nullptr, false);
800 
801  PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
802  Py_DECREF(dclass_obj);
803  nassertr(dclass_this != nullptr, false);
804 
805  DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
806  Py_DECREF(dclass_this);
807 
808  // check if we should forward this update to the owner view
809  vector_uchar data = _di.get_remaining_bytes();
810  DCPacker packer;
811  packer.set_unpack_data((const char *)data.data(), data.size(), false);
812  int field_id = packer.raw_unpack_uint16();
813  DCField *field = dclass->get_field_by_index(field_id);
814  if (field->is_ownrecv()) {
815  // It's a good idea to ensure the reference count to distobjOV is
816  // raised while we call the update method--otherwise, the update
817  // method might get into trouble if it tried to delete the object from
818  // the doId2do map.
819  Py_INCREF(distobjOV);
820  // make a copy of the datagram iterator so that we can use the main
821  // iterator for the non-owner update
822  DatagramIterator _odi(_di);
823  dclass->receive_update(distobjOV, _odi);
824  Py_DECREF(distobjOV);
825 
826  if (PyErr_Occurred()) {
827 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
828  PyGILState_Release(gstate);
829 #endif
830  return false;
831  }
832  }
833  }
834 
835  // now pass the update to the visible view
836  PyObject *distobj = PyDict_GetItem(doId2do, doId);
837  Py_DECREF(doId);
838  Py_DECREF(doId2do);
839 
840  if (distobj != nullptr) {
841  PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
842  nassertr(dclass_obj != nullptr, false);
843 
844  PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
845  Py_DECREF(dclass_obj);
846  nassertr(dclass_this != nullptr, false);
847 
848  DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
849  Py_DECREF(dclass_this);
850 
851  // check if we should forward this update to the owner view
852  vector_uchar data = _di.get_remaining_bytes();
853  DCPacker packer;
854  packer.set_unpack_data((const char *)data.data(), data.size(), false);
855 
856  //int field_id = packer.raw_unpack_uint16();
857  //DCField *field = dclass->get_field_by_index(field_id);
858  if (true) {//field->is_broadcast()) {
859  // It's a good idea to ensure the reference count to distobj is raised
860  // while we call the update method--otherwise, the update method might
861  // get into trouble if it tried to delete the object from the doId2do
862  // map.
863  Py_INCREF(distobj);
864  dclass->receive_update(distobj, _di);
865  Py_DECREF(distobj);
866 
867  if (PyErr_Occurred()) {
868 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
869  PyGILState_Release(gstate);
870 #endif
871  return false;
872  }
873  }
874  }
875  }
876 
877 #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
878  PyGILState_Release(gstate);
879 #endif
880 #endif // HAVE_PYTHON
881 
882  return true;
883 }
884 
885 /**
886  * Unpacks the message and reformats it for user consumption, writing a
887  * description on the indicated output stream.
888  */
889 void CConnectionRepository::
890 describe_message(std::ostream &out, const string &prefix,
891  const Datagram &dg) const {
892  DCPacker packer;
893 
894  packer.set_unpack_data((const char *)dg.get_data(), dg.get_length(), false);
895  CHANNEL_TYPE do_id;
896  int msg_type;
897  bool is_update = false;
898  string full_prefix = "CR::" + prefix;
899 
900  if (!_client_datagram)
901  {
902  unsigned char mcnt = packer.raw_unpack_uint8();
903  for( ;mcnt > 0; mcnt--)
904  packer.RAW_UNPACK_CHANNEL(); // msg_channel
905 
906  packer.RAW_UNPACK_CHANNEL(); // msg_sender
907  msg_type = packer.raw_unpack_uint16();
908  is_update = (msg_type == STATESERVER_OBJECT_SET_FIELD);
909 
910  } else {
911  msg_type = packer.raw_unpack_uint16();
912  is_update = (msg_type == CLIENT_OBJECT_SET_FIELD);
913  }
914 
915  if (!is_update) {
916  // figure out the name of the message TODO: print out the arguments to the
917  // message
918  string msgName;
919 
920  #ifdef HAVE_PYTHON
921  if (_python_repository != nullptr) {
922  PyObject *msgId = PyLong_FromLong(msg_type);
923  nassertv(msgId != nullptr);
924 #if PY_MAJOR_VERSION >= 3
925  PyObject *methodName = PyUnicode_FromString("_getMsgName");
926 #else
927  PyObject *methodName = PyString_FromString("_getMsgName");
928 #endif
929  nassertv(methodName != nullptr);
930 
931  PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
932  msgId, nullptr);
933  nassertv(result != nullptr);
934 
935 #if PY_MAJOR_VERSION >= 3
936  msgName += string(PyUnicode_AsUTF8(result));
937 #else
938  msgName += string(PyString_AsString(result));
939 #endif
940 
941  Py_DECREF(methodName);
942  Py_DECREF(msgId);
943  Py_DECREF(result);
944  }
945  #endif
946  if (msgName.length() == 0) {
947  msgName += "unknown message ";
948  msgName += msg_type;
949  msgName += "\n";
950  }
951  out << full_prefix << ":" << msgName << "\n";
952  dg.dump_hex(out, 2);
953 
954  } else {
955  // It's an update message. Figure out what dclass the object is based on
956  // its doId, so we can decode the rest of the message.
957  do_id = packer.raw_unpack_uint32();
958  DCClass *dclass = nullptr;
959 
960  #ifdef HAVE_PYTHON
961  if (_python_repository != nullptr) {
962  PyObject *doId2do =
963  PyObject_GetAttrString(_python_repository, "doId2do");
964  nassertv(doId2do != nullptr);
965 
966  #ifdef USE_PYTHON_2_2_OR_EARLIER
967  PyObject *doId = PyInt_FromLong(do_id);
968  #else
969  PyObject *doId = PyLong_FromUnsignedLong(do_id);
970  #endif
971  PyObject *distobj = PyDict_GetItem(doId2do, doId);
972  Py_DECREF(doId);
973  Py_DECREF(doId2do);
974 
975  if (distobj != nullptr) {
976  PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
977  nassertv(dclass_obj != nullptr);
978 
979  PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
980  Py_DECREF(dclass_obj);
981  nassertv(dclass_this != nullptr);
982 
983  dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
984  Py_DECREF(dclass_this);
985  }
986  }
987  #endif // HAVE_PYTHON
988 
989  int field_id = packer.raw_unpack_uint16();
990 
991  if (dclass == nullptr) {
992  out << full_prefix << "update for unknown object " << do_id
993  << ", field " << field_id << "\n";
994 
995  } else {
996  out << full_prefix <<
997  ":" << dclass->get_name() << "(" << do_id << ").";
998  DCField *field = dclass->get_field_by_index(field_id);
999  if (field == nullptr) {
1000  out << "unknown field " << field_id << "\n";
1001 
1002  } else {
1003  out << field->get_name();
1004  packer.begin_unpack(field);
1005  packer.unpack_and_format(out);
1006  packer.end_unpack();
1007  out << "\n";
1008  }
1009  }
1010  }
1011 }
vector_uchar get_remaining_bytes() const
Returns the remaining bytes in the datagram as a string, but does not extract them from the iterator.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
uint64_t get_uint64()
Extracts an unsigned 64-bit integer.
uint8_t get_uint8()
Extracts an unsigned 8-bit integer.
const std::string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
A container for a URL, e.g.
Definition: urlSpec.h:28
void abandon_message_bundles()
throw out any msgs that have been queued up for message bundles
void dump_hex(std::ostream &out, unsigned int indent=0) const
Writes a representation of the entire datagram contents, as a sequence of hex (and ASCII) values.
Definition: datagram.cxx:44
bool get_want_message_bundling() const
Returns true if message bundling enabled.
A single field of a Distributed Class, either atomic or molecular.
Definition: dcField.h:37
unsigned int raw_unpack_uint16()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:897
void disconnect()
Closes the connection to the server.
bool send_datagram(const Datagram &dg)
Queues the indicated datagram for sending to the server.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool get_verbose() const
Returns the current setting of the verbose flag.
Defines a particular DistributedClass as read from an input .dc file.
Definition: dcClass.h:44
bool consider_flush()
Sends the most recently queued data if enough time has elapsed.
static const std::string & get_overflow_event_name()
Returns event string that will be thrown if the datagram reader queue overflows.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void start_message_bundle()
Send a set of messages to the state server that will be processed atomically.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DCField * get_field_by_index(int index_number) const
Returns a pointer to the DCField that has the indicated index number.
Definition: dcClass.cxx:228
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool is_connected()
Returns true if the connection to the gameserver is established and still good, false if we are not c...
bool is_bundling_messages() const
Returns true if repository is queueing outgoing messages into a message bundle.
A lightweight class that represents a single element that may be timed and/or counted via stats.
unsigned int raw_unpack_uint8()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:887
void add_int8(int8_t value)
Adds a signed 8-bit integer to the datagram.
Definition: datagram.I:42
unsigned int raw_unpack_uint32()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:907
void set_unpack_data(const vector_uchar &data)
Sets up the unpack_data pointer.
Definition: dcPacker.cxx:117
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:219
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
bool is_ownrecv() const
Returns true if the "ownrecv" flag is set for this field, false otherwise.
Definition: dcField.I:125
Similar to MutexHolder, but for a reentrant mutex.
Definition: reMutexHolder.h:25
std::string unpack_and_format(bool show_field_names=true)
Unpacks an object and formats its value into a syntax suitable for parsing in the dc file (e....
Definition: dcPacker.cxx:989
A simple place to store and manipulate tcp and port address for communication layer.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_port
Returns the port number specified by the URL, or the default port if not specified.
Definition: urlSpec.h:97
void shutdown()
May be called at application shutdown to ensure all threads are cleaned up.
const std::string & get_name() const
Returns the name of this class.
Definition: dcClass.I:26
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
void set_tcp_header_size(int tcp_header_size)
Sets the header size of TCP packets.
This class can be used for packing a series of numeric and string data into a binary stream,...
Definition: dcPacker.h:34
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_server
Returns the server name specified by the URL, if any.
Definition: urlSpec.h:96
std::string get_message() const
Returns the datagram's data as a string.
Definition: datagram.I:314
void send_message_bundle(unsigned int channel, unsigned int sender_channel)
Send network messages queued up since startMessageBundle was called.
A class to retrieve the individual data elements previously stored in a Datagram.
bool set_host(const std::string &hostname, unsigned short port)
This function will take a port and string-based TCP address and initialize the address with this info...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool flush()
Sends the most recently queued data now.
void add_uint64(uint64_t value)
Adds an unsigned 64-bit integer to the datagram.
Definition: datagram.I:103
Represents a single TCP or UDP socket for input or output.
Definition: connection.h:29
bool check_datagram()
Returns true if a new datagram is available, false otherwise.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
void begin_unpack(const DCPackerInterface *root)
Begins an unpacking session.
Definition: dcPacker.cxx:153
bool end_unpack()
Finishes the unpacking session.
Definition: dcPacker.cxx:179
size_t get_length() const
Returns the number of bytes in the datagram.
Definition: datagram.I:335
const void * get_data() const
Returns a pointer to the beginning of the datagram's data.
Definition: datagram.I:327