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  */
252 check_datagram() {
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  */
343 is_connected() {
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  */
389 send_datagram(const Datagram &dg) {
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  */
539 consider_flush() {
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  */
601 disconnect() {
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  */
632 shutdown() {
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 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool flush()
Sends the most recently queued data now.
void abandon_message_bundles()
throw out any msgs that have been queued up for message bundles
bool is_bundling_messages() const
Returns true if repository is queueing outgoing messages into a message bundle.
bool get_want_message_bundling() const
Returns true if message bundling enabled.
bool is_connected()
Returns true if the connection to the gameserver is established and still good, false if we are not c...
void disconnect()
Closes the connection to the server.
bool send_datagram(const Datagram &dg)
Queues the indicated datagram for sending to the server.
void shutdown()
May be called at application shutdown to ensure all threads are cleaned up.
static const std::string & get_overflow_event_name()
Returns event string that will be thrown if the datagram reader queue overflows.
void set_tcp_header_size(int tcp_header_size)
Sets the header size of TCP packets.
bool check_datagram()
Returns true if a new datagram is available, false otherwise.
bool consider_flush()
Sends the most recently queued data if enough time has elapsed.
void send_message_bundle(unsigned int channel, unsigned int sender_channel)
Send network messages queued up since startMessageBundle was called.
bool get_verbose() const
Returns the current setting of the verbose flag.
void start_message_bundle()
Send a set of messages to the state server that will be processed atomically.
Represents a single TCP or UDP socket for input or output.
Definition: connection.h:29
Defines a particular DistributedClass as read from an input .dc file.
Definition: dcClass.h:44
const std::string & get_name() const
Returns the name of this class.
Definition: dcClass.I:26
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
A single field of a Distributed Class, either atomic or molecular.
Definition: dcField.h:37
bool is_ownrecv() const
Returns true if the "ownrecv" flag is set for this field, false otherwise.
Definition: dcField.I:125
const std::string & get_name() const
Returns the name of this field, or empty string if the field is unnamed.
This class can be used for packing a series of numeric and string data into a binary stream,...
Definition: dcPacker.h:34
unsigned int raw_unpack_uint32()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:907
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
void set_unpack_data(const std::vector< unsigned char > &data)
Sets up the unpack_data pointer.
Definition: dcPacker.cxx:117
unsigned int raw_unpack_uint8()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:887
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
unsigned int raw_unpack_uint16()
Unpacks the data from the buffer between unpacking sessions.
Definition: dcPacker.I:897
A class to retrieve the individual data elements previously stored in a Datagram.
uint8_t get_uint8()
Extracts an unsigned 8-bit integer.
uint64_t get_uint64()
Extracts an unsigned 64-bit integer.
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
vector_uchar get_remaining_bytes() const
Returns the remaining bytes in the datagram as a string, but does not extract them from the iterator.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
size_t get_length() const
Returns the number of bytes in the datagram.
Definition: datagram.I:335
std::string get_message() const
Returns the datagram's data as a string.
Definition: datagram.I:314
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
void add_uint64(uint64_t value)
Adds an unsigned 64-bit integer to the datagram.
Definition: datagram.I:103
void add_string(const std::string &str)
Adds a variable-length string to the datagram.
Definition: datagram.I:219
const void * get_data() const
Returns a pointer to the beginning of the datagram's data.
Definition: datagram.I:327
void add_int8(int8_t value)
Adds a signed 8-bit integer to the datagram.
Definition: datagram.I:42
A lightweight class that represents a single element that may be timed and/or counted via stats.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
Similar to MutexHolder, but for a reentrant mutex.
Definition: reMutexHolder.h:25
A simple place to store and manipulate tcp and port address for communication layer.
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...
A container for a URL, e.g.
Definition: urlSpec.h:28
get_server
Returns the server name specified by the URL, if any.
Definition: urlSpec.h:96
get_port
Returns the port number specified by the URL, or the default port if not specified.
Definition: urlSpec.h:97
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.