Panda3D
Loading...
Searching...
No Matches
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
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
31using std::endl;
32using std::string;
33
34const string CConnectionRepository::_overflow_event_name = "CRDatagramOverflow";
35
36#ifndef CPPPARSER
37PStatCollector CConnectionRepository::_update_pcollector("App:Show code:readerPollTask:Update");
38#endif // CPPPARSER
39
40/**
41 *
42 */
43CConnectionRepository::
44CConnectionRepository(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 */
85CConnectionRepository::
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 */
97set_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 */
118void CConnectionRepository::
119set_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 */
139SocketStream *CConnectionRepository::
140get_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 */
153bool CConnectionRepository::
154try_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 */
178bool CConnectionRepository::
179connect_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 */
206void CConnectionRepository::
207start_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 */
231void CConnectionRepository::
232stop_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 */
343is_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 */
389send_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 */
483send_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 */
526void CConnectionRepository::
527bundle_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 */
571flush() {
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 */
601disconnect() {
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 */
632shutdown() {
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 */
645bool CConnectionRepository::
646do_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 */
680bool CConnectionRepository::
681handle_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 PyObject *doId = PyLong_FromUnsignedLong(do_id);
697 PyObject *distobj = PyDict_GetItem(doId2do, doId);
698 Py_DECREF(doId);
699 Py_DECREF(doId2do);
700
701 if (distobj != nullptr) {
702 PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
703 nassertr(dclass_obj != nullptr, false);
704
705
706 PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
707 Py_DECREF(dclass_obj);
708 nassertr(dclass_this != nullptr, false);
709
710 DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
711 Py_DECREF(dclass_this);
712
713 // If in quiet zone mode, throw update away unless distobj has
714 // 'neverDisable' attribute set to non-zero
715 if (_in_quiet_zone) {
716 PyObject *neverDisable = PyObject_GetAttrString(distobj, "neverDisable");
717 nassertr(neverDisable != nullptr, false);
718
719 unsigned int cNeverDisable = PyLong_AsLong(neverDisable);
720 Py_DECREF(neverDisable);
721 if (!cNeverDisable) {
722 // in quiet zone and distobj is disable-able drop update on the
723 // floor
724#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
725 PyGILState_Release(gstate);
726#endif
727 return true;
728 }
729 }
730
731 // It's a good idea to ensure the reference count to distobj is raised
732 // while we call the update method--otherwise, the update method might
733 // get into trouble if it tried to delete the object from the doId2do
734 // map.
735 Py_INCREF(distobj);
736 dclass->receive_update(distobj, _di);
737 Py_DECREF(distobj);
738
739 if (PyErr_Occurred()) {
740#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
741 PyGILState_Release(gstate);
742#endif
743 return false;
744 }
745 }
746
747 }
748
749#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
750 PyGILState_Release(gstate);
751#endif
752 #endif // HAVE_PYTHON
753 return true;
754}
755
756
757/**
758 * Directly handles an update message on a field. Supports 'owner' views of
759 * objects, separate from 'visible' view, and forwards fields to the
760 * appropriate view(s) based on DC flags. Python never touches the datagram;
761 * it just gets its distributed method called with the appropriate parameters.
762 * Returns true if everything is ok, false if there was an error processing
763 * the field's update method.
764 */
765bool CConnectionRepository::
766handle_update_field_owner() {
767#ifdef HAVE_PYTHON
768#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
769 PyGILState_STATE gstate;
770 gstate = PyGILState_Ensure();
771#endif
772
773 PStatTimer timer(_update_pcollector);
774 unsigned int do_id = _di.get_uint32();
775 if (_python_repository != nullptr) {
776 PyObject *doId2do =
777 PyObject_GetAttrString(_python_repository, "doId2do");
778 nassertr(doId2do != nullptr, false);
779
780 PyObject *doId2ownerView =
781 PyObject_GetAttrString(_python_repository, "doId2ownerView");
782 nassertr(doId2ownerView != nullptr, false);
783
784 PyObject *doId = PyLong_FromUnsignedLong(do_id);
785
786 // pass the update to the owner view first
787 PyObject *distobjOV = PyDict_GetItem(doId2ownerView, doId);
788 Py_DECREF(doId2ownerView);
789
790 if (distobjOV != nullptr) {
791 PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
792 nassertr(dclass_obj != nullptr, false);
793
794 PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
795 Py_DECREF(dclass_obj);
796 nassertr(dclass_this != nullptr, false);
797
798 DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
799 Py_DECREF(dclass_this);
800
801 // check if we should forward this update to the owner view
802 vector_uchar data = _di.get_remaining_bytes();
803 DCPacker packer;
804 packer.set_unpack_data((const char *)data.data(), data.size(), false);
805 int field_id = packer.raw_unpack_uint16();
806 DCField *field = dclass->get_field_by_index(field_id);
807 if (field->is_ownrecv()) {
808 // It's a good idea to ensure the reference count to distobjOV is
809 // raised while we call the update method--otherwise, the update
810 // method might get into trouble if it tried to delete the object from
811 // the doId2do map.
812 Py_INCREF(distobjOV);
813 // make a copy of the datagram iterator so that we can use the main
814 // iterator for the non-owner update
815 DatagramIterator _odi(_di);
816 dclass->receive_update(distobjOV, _odi);
817 Py_DECREF(distobjOV);
818
819 if (PyErr_Occurred()) {
820#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
821 PyGILState_Release(gstate);
822#endif
823 return false;
824 }
825 }
826 }
827
828 // now pass the update to the visible view
829 PyObject *distobj = PyDict_GetItem(doId2do, doId);
830 Py_DECREF(doId);
831 Py_DECREF(doId2do);
832
833 if (distobj != nullptr) {
834 PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
835 nassertr(dclass_obj != nullptr, false);
836
837 PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
838 Py_DECREF(dclass_obj);
839 nassertr(dclass_this != nullptr, false);
840
841 DCClass *dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
842 Py_DECREF(dclass_this);
843
844 // check if we should forward this update to the owner view
845 vector_uchar data = _di.get_remaining_bytes();
846 DCPacker packer;
847 packer.set_unpack_data((const char *)data.data(), data.size(), false);
848
849 //int field_id = packer.raw_unpack_uint16();
850 //DCField *field = dclass->get_field_by_index(field_id);
851 if (true) {//field->is_broadcast()) {
852 // It's a good idea to ensure the reference count to distobj is raised
853 // while we call the update method--otherwise, the update method might
854 // get into trouble if it tried to delete the object from the doId2do
855 // map.
856 Py_INCREF(distobj);
857 dclass->receive_update(distobj, _di);
858 Py_DECREF(distobj);
859
860 if (PyErr_Occurred()) {
861#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
862 PyGILState_Release(gstate);
863#endif
864 return false;
865 }
866 }
867 }
868 }
869
870#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
871 PyGILState_Release(gstate);
872#endif
873#endif // HAVE_PYTHON
874
875 return true;
876}
877
878/**
879 * Unpacks the message and reformats it for user consumption, writing a
880 * description on the indicated output stream.
881 */
882void CConnectionRepository::
883describe_message(std::ostream &out, const string &prefix,
884 const Datagram &dg) const {
885 DCPacker packer;
886
887 packer.set_unpack_data((const char *)dg.get_data(), dg.get_length(), false);
888 CHANNEL_TYPE do_id;
889 unsigned int msg_type;
890 bool is_update = false;
891 string full_prefix = "CR::" + prefix;
892
893 if (!_client_datagram)
894 {
895 unsigned char mcnt = packer.raw_unpack_uint8();
896 for( ;mcnt > 0; mcnt--)
897 packer.RAW_UNPACK_CHANNEL(); // msg_channel
898
899 packer.RAW_UNPACK_CHANNEL(); // msg_sender
900 msg_type = packer.raw_unpack_uint16();
901 is_update = (msg_type == STATESERVER_OBJECT_SET_FIELD);
902
903 } else {
904 msg_type = packer.raw_unpack_uint16();
905 is_update = (msg_type == CLIENT_OBJECT_SET_FIELD);
906 }
907
908 if (!is_update) {
909 // figure out the name of the message TODO: print out the arguments to the
910 // message
911 string msgName;
912
913 #ifdef HAVE_PYTHON
914 if (_python_repository != nullptr) {
915#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
916 PyGILState_STATE gstate;
917 gstate = PyGILState_Ensure();
918#endif
919
920 PyObject *msgId = PyLong_FromUnsignedLong(msg_type);
921 nassertv(msgId != nullptr);
922#if PY_MAJOR_VERSION >= 3
923 PyObject *methodName = PyUnicode_FromString("_getMsgName");
924#else
925 PyObject *methodName = PyString_FromString("_getMsgName");
926#endif
927 nassertv(methodName != nullptr);
928
929 PyObject *result = PyObject_CallMethodObjArgs(_python_repository, methodName,
930 msgId, nullptr);
931 nassertv(result != nullptr);
932
933#if PY_MAJOR_VERSION >= 3
934 msgName += string(PyUnicode_AsUTF8(result));
935#else
936 msgName += string(PyString_AsString(result));
937#endif
938
939 Py_DECREF(methodName);
940 Py_DECREF(msgId);
941 Py_DECREF(result);
942
943#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
944 PyGILState_Release(gstate);
945#endif
946 }
947 #endif
948 if (msgName.length() == 0) {
949 msgName += "unknown message ";
950 msgName += msg_type;
951 msgName += "\n";
952 }
953 out << full_prefix << ":" << msgName << "\n";
954 dg.dump_hex(out, 2);
955
956 } else {
957 // It's an update message. Figure out what dclass the object is based on
958 // its doId, so we can decode the rest of the message.
959 do_id = packer.raw_unpack_uint32();
960 DCClass *dclass = nullptr;
961
962 #ifdef HAVE_PYTHON
963 if (_python_repository != nullptr) {
964#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
965 PyGILState_STATE gstate;
966 gstate = PyGILState_Ensure();
967#endif
968
969 PyObject *doId2do =
970 PyObject_GetAttrString(_python_repository, "doId2do");
971 nassertv(doId2do != nullptr);
972
973 PyObject *doId = PyLong_FromUnsignedLong(do_id);
974 PyObject *distobj = PyDict_GetItem(doId2do, doId);
975 Py_DECREF(doId);
976 Py_DECREF(doId2do);
977
978 if (distobj != nullptr) {
979 PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
980 nassertv(dclass_obj != nullptr);
981
982 PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
983 Py_DECREF(dclass_obj);
984 nassertv(dclass_this != nullptr);
985
986 dclass = (DCClass *)PyLong_AsVoidPtr(dclass_this);
987 Py_DECREF(dclass_this);
988 }
989
990#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
991 PyGILState_Release(gstate);
992#endif
993 }
994 #endif // HAVE_PYTHON
995
996 int field_id = packer.raw_unpack_uint16();
997
998 if (dclass == nullptr) {
999 out << full_prefix << "update for unknown object " << do_id
1000 << ", field " << field_id << "\n";
1001
1002 } else {
1003 out << full_prefix <<
1004 ":" << dclass->get_name() << "(" << do_id << ").";
1005 DCField *field = dclass->get_field_by_index(field_id);
1006 if (field == nullptr) {
1007 out << "unknown field " << field_id << "\n";
1008
1009 } else {
1010 out << field->get_name();
1011 packer.begin_unpack(field);
1012 packer.unpack_and_format(out);
1013 packer.end_unpack();
1014 out << "\n";
1015 }
1016 }
1017 }
1018}
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.
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.