From ac8417ffdf0f253d8c2653b9a27becee1833778c Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 4 Mar 2018 15:28:39 -0700 Subject: [PATCH 01/32] distributed: `type(x) == types.FooType` -> `inspect.isfoo(x)` This is more compatible across Python 2 vs. 3. [skip ci] --- direct/src/distributed/ConnectionRepository.py | 8 ++++---- direct/src/distributed/ServerRepository.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index 4d6dec5b81..974c611bcc 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -7,7 +7,7 @@ from direct.distributed.DoCollectionManager import DoCollectionManager from direct.showbase import GarbageReport from .PyDatagramIterator import PyDatagramIterator -import types +import inspect import gc __all__ = ["ConnectionRepository", "GCTrigger"] @@ -327,13 +327,13 @@ class ConnectionRepository( if classDef is None: self.notify.debug("No class definition for %s." % (className)) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.warning("Module %s does not define class %s." % (className, className)) continue classDef = getattr(classDef, className) - if type(classDef) != types.ClassType and type(classDef) != types.TypeType: + if inspect.isclass(classDef): self.notify.error("Symbol %s is not a class name." % (className)) else: dclass.setClassDef(classDef) @@ -388,7 +388,7 @@ class ConnectionRepository( if classDef is None: self.notify.error("No class definition for %s." % className) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.error("Module %s does not define class %s." % (className, className)) classDef = getattr(classDef, className) diff --git a/direct/src/distributed/ServerRepository.py b/direct/src/distributed/ServerRepository.py index 4031998b50..7783e2860b 100644 --- a/direct/src/distributed/ServerRepository.py +++ b/direct/src/distributed/ServerRepository.py @@ -7,6 +7,8 @@ from direct.task import Task from direct.directnotify import DirectNotifyGlobal from direct.distributed.PyDatagram import PyDatagram +import inspect + class ServerRepository: @@ -273,12 +275,12 @@ class ServerRepository: if classDef == None: self.notify.debug("No class definition for %s." % (className)) else: - if type(classDef) == types.ModuleType: + if inspect.ismodule(classDef): if not hasattr(classDef, className): self.notify.error("Module %s does not define class %s." % (className, className)) classDef = getattr(classDef, className) - if type(classDef) != types.ClassType and type(classDef) != types.TypeType: + if inspect.isclass(classDef): self.notify.error("Symbol %s is not a class name." % (className)) else: dclass.setClassDef(classDef) From 013af2ac4b5f9a0804864b90fa57e5108f98c469 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 4 Mar 2018 20:28:09 -0700 Subject: [PATCH 02/32] dtool: Delete 'newheader' This is a task better handled by editors/scripts. [skip ci] --- dtool/src/newheader/newheader.cxx | 108 ------------------------------ 1 file changed, 108 deletions(-) delete mode 100644 dtool/src/newheader/newheader.cxx diff --git a/dtool/src/newheader/newheader.cxx b/dtool/src/newheader/newheader.cxx deleted file mode 100644 index 2e97dbb031..0000000000 --- a/dtool/src/newheader/newheader.cxx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * PANDA 3D SOFTWARE - * Copyright (c) Carnegie Mellon University. All rights reserved. - * - * All use of this software is subject to the terms of the revised BSD - * license. You should have received a copy of this license along - * with this source code in a file named "LICENSE." - * - * @file newheader.cxx - * @author drose - * @date 2004-07-05 - */ - -#include "dtoolbase.h" - -#include -#include -#include - -const char *cxx_style = -"// Filename: %s\n" -"// Created by: %s (%s)\n" -"//\n" -"////////////////////////////////////////////////////////////////////\n" -"//\n" -"// PANDA 3D SOFTWARE\n" -"// Copyright (c) Carnegie Mellon University. All rights reserved.\n" -"//\n" -"// All use of this software is subject to the terms of the revised BSD\n" -"// license. You should have received a copy of this license along\n" -"// with this source code in a file named \"LICENSE.\"\n" -"//\n" -"////////////////////////////////////////////////////////////////////\n" -"\n"; - -const char *c_style = -"/* Filename: %s\n" -" * Created by: %s (%s)\n" -" *\n" -" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" -" *\n" -" * PANDA 3D SOFTWARE\n" -" * Copyright (c) Carnegie Mellon University. All rights reserved.\n" -" *\n" -" * All use of this software is subject to the terms of the revised BSD\n" -" * license. You should have received a copy of this license along\n" -" * with this source code in a file named \"LICENSE.\"\n" -" *\n" -" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */\n" -"\n"; - -struct FileDef { - const char *extension; - const char *header; -}; - -FileDef file_def[] = { - { "h", cxx_style }, - { "cxx", cxx_style }, - { "I", cxx_style }, - { "T", cxx_style }, - { "c", c_style }, - { NULL, NULL }, -}; - -void -generate_header(const char *header, const string &filename) { - const char *username = getenv("USER"); - if (username == NULL) { - username = ""; - } - - static const size_t max_date_buffer = 128; - char date_buffer[max_date_buffer]; - time_t now = time(NULL); - strftime(date_buffer, max_date_buffer, "%d%b%y", localtime(&now)); - - printf(header, filename.c_str(), username, date_buffer); -} - -int -main(int argc, char *argv[]) { - if (argc < 2) { - cerr << "Must specify the filename to generate a header for.\n"; - exit(1); - } - - string filename = argv[1]; - size_t dot = filename.rfind('.'); - if (dot == string::npos) { - // No extension, no header. - return 0; - } - - string extension = filename.substr(dot + 1); - - size_t i = 0; - while (file_def[i].extension != NULL) { - if (extension == file_def[i].extension) { - generate_header(file_def[i].header, filename); - return 0; - } - i++; - } - - // No matching extension, no problem. - return 0; -} From ebe9e75d849600a061fad31e2a2930811a1cb92b Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 4 Mar 2018 20:28:51 -0700 Subject: [PATCH 03/32] dtoolbase: Delete unused preprocessor macro [skip ci] --- dtool/src/dtoolbase/dtoolsymbols.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/dtool/src/dtoolbase/dtoolsymbols.h b/dtool/src/dtoolbase/dtoolsymbols.h index 971a13ce32..892e1a9fff 100644 --- a/dtool/src/dtoolbase/dtoolsymbols.h +++ b/dtool/src/dtoolbase/dtoolsymbols.h @@ -67,8 +67,6 @@ can define all of these stupid symbols to the empty string. */ -#define EXPCL_EMPTY - #ifdef BUILDING_DTOOL #define EXPCL_DTOOL EXPORT_CLASS #define EXPTP_DTOOL EXPORT_TEMPL From d088341da1a45cf47d27988d27c1a516415d2127 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 5 Mar 2018 02:22:20 -0700 Subject: [PATCH 04/32] bam: Log object IDs when spamming read --- panda/src/putil/bamReader.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panda/src/putil/bamReader.cxx b/panda/src/putil/bamReader.cxx index 30ad07415a..3ab1386d7f 100644 --- a/panda/src/putil/bamReader.cxx +++ b/panda/src/putil/bamReader.cxx @@ -1278,7 +1278,8 @@ p_read_object() { } else { if (bam_cat.is_spam()) { bam_cat.spam() - << "Read a " << object->get_type() << ": " << (void *)object << "\n"; + << "Read a " << object->get_type() << ": " << (void *)object + << " (id=" << object_id << ")\n"; } } } From 74bb2fef2e20d743974f696579e00bab5d48ebd3 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 5 Mar 2018 04:05:19 -0700 Subject: [PATCH 05/32] bam: Fix typo in ClipPlaneAttrib::fillin --- panda/src/pgraph/clipPlaneAttrib.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/pgraph/clipPlaneAttrib.cxx b/panda/src/pgraph/clipPlaneAttrib.cxx index 2b45be735a..aca61c8bc9 100644 --- a/panda/src/pgraph/clipPlaneAttrib.cxx +++ b/panda/src/pgraph/clipPlaneAttrib.cxx @@ -1017,7 +1017,7 @@ fillin(DatagramIterator &scan, BamReader *manager) { _on_planes.resize(num_on_planes); if (manager->get_file_minor_ver() >= 40) { for (int i = 0; i < num_on_planes; i++) { - manager->read_pointer(scan); + _on_planes[i].fillin(scan, manager); } } else { manager->read_pointers(scan, num_on_planes); From fd6eebb7fe88651e71219f70b7a9d2286d0f0043 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 5 Mar 2018 05:21:30 -0700 Subject: [PATCH 06/32] bam: Fix circular reference with ClipPlaneAttrib What's being addressed here is the circumstance where an ancestor of a PlaneNode has a ClipPlaneAttrib that references said PlaneNode. The code here is just being copied out of LightAttrib, which has the exact same mode of operation (it nominates a sorted list of on/off NodePaths) and a compatible structure. LightAttrib has had this problem in the past, so using the same solution makes sense. --- panda/src/pgraph/clipPlaneAttrib.cxx | 135 ++++++++++++++++----------- panda/src/pgraph/clipPlaneAttrib.h | 17 +++- 2 files changed, 99 insertions(+), 53 deletions(-) diff --git a/panda/src/pgraph/clipPlaneAttrib.cxx b/panda/src/pgraph/clipPlaneAttrib.cxx index aca61c8bc9..726b5472b7 100644 --- a/panda/src/pgraph/clipPlaneAttrib.cxx +++ b/panda/src/pgraph/clipPlaneAttrib.cxx @@ -900,12 +900,52 @@ write_datagram(BamWriter *manager, Datagram &dg) { int ClipPlaneAttrib:: complete_pointers(TypedWritable **p_list, BamReader *manager) { int pi = RenderAttrib::complete_pointers(p_list, manager); - AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); if (manager->get_file_minor_ver() >= 40) { for (size_t i = 0; i < _off_planes.size(); ++i) { pi += _off_planes[i].complete_pointers(p_list + pi, manager); + } + for (size_t i = 0; i < _on_planes.size(); ++i) { + pi += _on_planes[i].complete_pointers(p_list + pi, manager); + } + + } else { + BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes"); + nassertr(aux != NULL, pi); + + int i; + aux->_off_list.reserve(aux->_num_off_planes); + for (i = 0; i < aux->_num_off_planes; ++i) { + PandaNode *node; + DCAST_INTO_R(node, p_list[pi++], pi); + aux->_off_list.push_back(node); + } + + aux->_on_list.reserve(aux->_num_on_planes); + for (i = 0; i < aux->_num_on_planes; ++i) { + PandaNode *node; + DCAST_INTO_R(node, p_list[pi++], pi); + aux->_on_list.push_back(node); + } + } + + return pi; +} + +/** + * Called by the BamReader to perform any final actions needed for setting up + * the object after all objects have been read and all pointers have been + * completed. + */ +void ClipPlaneAttrib:: +finalize(BamReader *manager) { + if (manager->get_file_minor_ver() >= 40) { + AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); + + // Check if any of the nodes we loaded are mentioned in the + // AttribNodeRegistry. If so, replace them. + for (size_t i = 0; i < _off_planes.size(); ++i) { int n = areg->find_node(_off_planes[i]); if (n != -1) { // If it's in the registry, replace it. @@ -914,8 +954,6 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) { } for (size_t i = 0; i < _on_planes.size(); ++i) { - pi += _on_planes[i].complete_pointers(p_list + pi, manager); - int n = areg->find_node(_on_planes[i]); if (n != -1) { // If it's in the registry, replace it. @@ -924,53 +962,46 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) { } } else { - Planes::iterator ci = _off_planes.begin(); - while (ci != _off_planes.end()) { - PandaNode *node; - DCAST_INTO_R(node, p_list[pi++], pi); + // Now it's safe to convert our saved PandaNodes into NodePaths. + BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes"); + nassertv(aux != NULL); + nassertv(aux->_num_off_planes == (int)aux->_off_list.size()); + nassertv(aux->_num_on_planes == (int)aux->_on_list.size()); - // We go through some effort to look up the node in the registry without - // creating a NodePath around it first (which would up, and then down, - // the reference count, possibly deleting the node). - int ni = areg->find_node(node->get_type(), node->get_name()); - if (ni != -1) { - (*ci) = areg->get_node(ni); + AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr(); + + _off_planes.reserve(aux->_off_list.size()); + NodeList::iterator ni; + for (ni = aux->_off_list.begin(); ni != aux->_off_list.end(); ++ni) { + PandaNode *node = (*ni); + int n = areg->find_node(node->get_type(), node->get_name()); + if (n != -1) { + // If it's in the registry, add that NodePath. + _off_planes.push_back(areg->get_node(n)); } else { - (*ci) = NodePath(node); + // Otherwise, add any arbitrary NodePath. Complain if it's ambiguous. + _off_planes.push_back(NodePath(node)); } - ++ci; } - ci = _on_planes.begin(); - while (ci != _on_planes.end()) { - PandaNode *node; - DCAST_INTO_R(node, p_list[pi++], pi); - - int ni = areg->find_node(node->get_type(), node->get_name()); - if (ni != -1) { - (*ci) = areg->get_node(ni); + _on_planes.reserve(aux->_on_list.size()); + for (ni = aux->_on_list.begin(); ni != aux->_on_list.end(); ++ni) { + PandaNode *node = (*ni); + int n = areg->find_node(node->get_type(), node->get_name()); + if (n != -1) { + // If it's in the registry, add that NodePath. + _on_planes.push_back(areg->get_node(n)); + node = _on_planes.back().node(); } else { - (*ci) = NodePath(node); + // Otherwise, add any arbitrary NodePath. Complain if it's ambiguous. + _on_planes.push_back(NodePath(node)); } - ++ci; } } + // Now that the NodePaths have been filled in, we can sort the list. _off_planes.sort(); _on_planes.sort(); - - return pi; -} - -/** - * Some objects require all of their nested pointers to have been completed - * before the objects themselves can be completed. If this is the case, - * override this method to return true, and be careful with circular - * references (which would make the object unreadable from a bam file). - */ -bool ClipPlaneAttrib:: -require_fully_complete() const { - return true; } /** @@ -987,6 +1018,8 @@ make_from_bam(const FactoryParams ¶ms) { parse_params(params, scan, manager); attrib->fillin(scan, manager); + manager->register_finalize(attrib); + return attrib; } @@ -1000,26 +1033,24 @@ fillin(DatagramIterator &scan, BamReader *manager) { _off_all_planes = scan.get_bool(); - int num_off_planes = scan.get_uint16(); - - // Push back an empty NodePath for each off Plane for now, until we get the - // actual list of pointers later in complete_pointers(). - _off_planes.resize(num_off_planes); if (manager->get_file_minor_ver() >= 40) { - for (int i = 0; i < num_off_planes; i++) { + _off_planes.resize(scan.get_uint16()); + for (size_t i = 0; i < _off_planes.size(); ++i) { _off_planes[i].fillin(scan, manager); } - } else { - manager->read_pointers(scan, num_off_planes); - } - int num_on_planes = scan.get_uint16(); - _on_planes.resize(num_on_planes); - if (manager->get_file_minor_ver() >= 40) { - for (int i = 0; i < num_on_planes; i++) { + _on_planes.resize(scan.get_uint16()); + for (size_t i = 0; i < _on_planes.size(); ++i) { _on_planes[i].fillin(scan, manager); } } else { - manager->read_pointers(scan, num_on_planes); + BamAuxData *aux = new BamAuxData; + manager->set_aux_data(this, "planes", aux); + + aux->_num_off_planes = scan.get_uint16(); + manager->read_pointers(scan, aux->_num_off_planes); + + aux->_num_on_planes = scan.get_uint16(); + manager->read_pointers(scan, aux->_num_on_planes); } } diff --git a/panda/src/pgraph/clipPlaneAttrib.h b/panda/src/pgraph/clipPlaneAttrib.h index ca95233b79..b59bb34f9b 100644 --- a/panda/src/pgraph/clipPlaneAttrib.h +++ b/panda/src/pgraph/clipPlaneAttrib.h @@ -125,11 +125,26 @@ PUBLISHED: } MAKE_PROPERTY(class_slot, get_class_slot); +public: + // This data is only needed when reading from a bam file. + typedef pvector NodeList; + class BamAuxData : public BamReader::AuxData { + public: + // We hold a pointer to each of the PandaNodes on the on_list and + // off_list. We will later convert these to NodePaths in + // finalize(). + int _num_off_planes; + int _num_on_planes; + NodeList _off_list; + NodeList _on_list; + }; + public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); virtual int complete_pointers(TypedWritable **plist, BamReader *manager); - virtual bool require_fully_complete() const; + + virtual void finalize(BamReader *manager); protected: static TypedWritable *make_from_bam(const FactoryParams ¶ms); From 1b1c76b2e84bc4aa27a978d8f071c482e3275ac2 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Mon, 5 Mar 2018 06:10:11 -0700 Subject: [PATCH 07/32] pgraph: Fix "Unknown render mode 5" errors This happened when a M_dual transparent object is given the M_filled_wireframe render attrib. M_dual would copy the transparent parts of the object to the transparent back-to-front bin, before the M_filled_wireframe handler could deal with the M_filled_wireframe flag. The solution is just to switch the order - let M_filled_wireframe be dealt with before the transparency code gets a chance to make a copy. --- panda/src/pgraph/cullResult.cxx | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/panda/src/pgraph/cullResult.cxx b/panda/src/pgraph/cullResult.cxx index 0118888108..7dbe002c9e 100644 --- a/panda/src/pgraph/cullResult.cxx +++ b/panda/src/pgraph/cullResult.cxx @@ -128,6 +128,29 @@ add_object(CullableObject *object, const CullTraverser *traverser) { object->_state = object->_state->compose(get_rescale_normal_state(mode)); } + // Check for a special wireframe setting. + const RenderModeAttrib *rmode; + if (object->_state->get_attrib(rmode)) { + if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) { + CullableObject *wireframe_part = new CullableObject(*object); + wireframe_part->_state = get_wireframe_overlay_state(rmode); + + if (wireframe_part->munge_geom + (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread), + traverser, force)) { + int wireframe_bin_index = bin_manager->find_bin("fixed"); + CullBin *bin = get_bin(wireframe_bin_index); + nassertv(bin != (CullBin *)NULL); + check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index); + bin->add_object(wireframe_part, current_thread); + } else { + delete wireframe_part; + } + + object->_state = object->_state->compose(get_wireframe_filled_state()); + } + } + // Check to see if there's a special transparency setting. const TransparencyAttrib *trans; if (object->_state->get_attrib(trans)) { @@ -216,29 +239,6 @@ add_object(CullableObject *object, const CullTraverser *traverser) { } } - // Check for a special wireframe setting. - const RenderModeAttrib *rmode; - if (object->_state->get_attrib(rmode)) { - if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) { - CullableObject *wireframe_part = new CullableObject(*object); - wireframe_part->_state = get_wireframe_overlay_state(rmode); - - if (wireframe_part->munge_geom - (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread), - traverser, force)) { - int wireframe_bin_index = bin_manager->find_bin("fixed"); - CullBin *bin = get_bin(wireframe_bin_index); - nassertv(bin != (CullBin *)NULL); - check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index); - bin->add_object(wireframe_part, current_thread); - } else { - delete wireframe_part; - } - - object->_state = object->_state->compose(get_wireframe_filled_state()); - } - } - int bin_index = object->_state->get_bin_index(); CullBin *bin = get_bin(bin_index); nassertv(bin != (CullBin *)NULL); From 89799bc024fbc3092ef40bf7a0bb735044c4aec7 Mon Sep 17 00:00:00 2001 From: Michael Wass Date: Mon, 5 Mar 2018 20:32:27 -0500 Subject: [PATCH 08/32] direct: Fix some NameErrors Squashed merge of GitHub PR #273 --- direct/src/interval/MetaInterval.py | 1 - direct/src/showbase/ObjectPool.py | 9 --------- 2 files changed, 10 deletions(-) diff --git a/direct/src/interval/MetaInterval.py b/direct/src/interval/MetaInterval.py index fd379280a7..259d7f66a3 100644 --- a/direct/src/interval/MetaInterval.py +++ b/direct/src/interval/MetaInterval.py @@ -342,7 +342,6 @@ class MetaInterval(CMetaInterval): # with all of their associated Python callbacks: def setManager(self, manager): - rogerroger self.__manager = manager CMetaInterval.setManager(self, manager) diff --git a/direct/src/showbase/ObjectPool.py b/direct/src/showbase/ObjectPool.py index e5970803ab..e82e6cdaae 100755 --- a/direct/src/showbase/ObjectPool.py +++ b/direct/src/showbase/ObjectPool.py @@ -110,15 +110,6 @@ class ObjectPool: print('TYPE: %s, %s objects' % (repr(typ), len(self._type2objs[typ]))) print(getNumberedTypedSortedString(self._type2objs[typ])) - def containerLenStr(self): - s = 'Object Pool: Container Lengths' - s += '\n==============================' - lengths = list(self._len2obj.keys()) - lengths.sort() - lengths.reverse() - for count in counts: - pass - def printReferrers(self, numEach=3): """referrers of the first few of each type of object""" counts = list(set(self._count2types.keys())) From 1862100baca2c8d5e3a4e0f2268ba30bcf7b7b9e Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 1 Mar 2018 14:55:41 -0700 Subject: [PATCH 09/32] openal: Don't assume alSourceUnqueueBuffers is FIFO --- panda/src/audiotraits/openalAudioSound.cxx | 40 +++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index d3c5ff7291..da04514e8b 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -446,18 +446,34 @@ pull_used_buffers() { int err = alGetError(); if (err == AL_NO_ERROR) { if (_stream_queued[0]._buffer != buffer) { - audio_error("corruption in stream queue"); - cleanup(); - return; - } - _stream_queued.pop_front(); - if (_stream_queued.size()) { - double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length; - double rtc = TrueClock::get_global_ptr()->get_short_time(); - correct_calibrated_clock(rtc, al); - } - if (buffer != _sd->_sample) { - alDeleteBuffers(1,&buffer); + // This is certainly atypical: most implementations of OpenAL unqueue + // buffers in FIFO order. However, some (e.g. Apple's) can unqueue + // buffers out-of-order if playback is interrupted. So, we don't freak + // out unless `buffer` isn't in _stream_queued at all. + bool found_culprit = false; + for (auto it = _stream_queued.begin(); it != _stream_queued.end(); ++it) { + if (it->_buffer == buffer) { + // Phew. Found it. Just remove that. + _stream_queued.erase(it); + found_culprit = true; + break; + } + } + if (!found_culprit) { + audio_error("corruption in stream queue"); + cleanup(); + return; + } + } else { + _stream_queued.pop_front(); + if (_stream_queued.size()) { + double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length; + double rtc = TrueClock::get_global_ptr()->get_short_time(); + correct_calibrated_clock(rtc, al); + } + if (buffer != _sd->_sample) { + alDeleteBuffers(1,&buffer); + } } } else { break; From 6ca0a68042c8affa4f105db0d6b00695418b2fbb Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 1 Mar 2018 16:44:46 -0700 Subject: [PATCH 10/32] openal: Retry deleting a buffer until success The rationale for this change is Apple's OpenAL implementation, which needs a little time after the `alSourcei(source, AL_BUFFER, 0);` call before any buffers used by that source are free for deletion. The defaults in the config variables are such that the OpenAL manager will attempt to delete a buffer up to 6 times (that is, the original attempt plus 5 reattempts), with delays of 1ms, 2ms, 4ms, 8ms, and 16ms before each reattempt - which means it'll wait a grand total of 31ms for a buffer to be free before assuming that some even greater problem must be happening and giving up. --- panda/src/audiotraits/config_openalAudio.cxx | 17 ++++++++ panda/src/audiotraits/config_openalAudio.h | 2 + panda/src/audiotraits/openalAudioManager.cxx | 41 +++++++++++++++++++- panda/src/audiotraits/openalAudioManager.h | 2 + panda/src/audiotraits/openalAudioSound.cxx | 5 +-- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/panda/src/audiotraits/config_openalAudio.cxx b/panda/src/audiotraits/config_openalAudio.cxx index 4f9019c013..36803e7d04 100644 --- a/panda/src/audiotraits/config_openalAudio.cxx +++ b/panda/src/audiotraits/config_openalAudio.cxx @@ -30,6 +30,23 @@ ConfigVariableString openal_device PRC_DESC("Specify the OpenAL device string for audio playback (no quotes). If this " "is not specified, the OpenAL default device is used.")); +ConfigVariableInt openal_buffer_delete_reattempts +("openal-buffer-delete-reattempts", 5, + PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " + "sound plugin will wait a moment and reattempt deletion, with an " + "exponentially-increasing delay for each attempt. This number " + "specifies how many repeat attempts (not counting the initial attempt) " + "should be made before giving up and raising an error.")); + +ConfigVariableDouble openal_buffer_delete_delay +("openal-buffer-delete-delay", 0.001, + PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " + "sound plugin will wait a moment and reattempt deletion, with an " + "exponentially-increasing delay for each attempt. This number " + "specifies how long, in seconds, the OpenAL plugin will wait after " + "its first failed attempt. The second attempt will be double this " + "delay, the third quadruple, and so on.")); + /** * Initializes the library. This must be called at least once before any of diff --git a/panda/src/audiotraits/config_openalAudio.h b/panda/src/audiotraits/config_openalAudio.h index bcd8481acf..96d3429519 100644 --- a/panda/src/audiotraits/config_openalAudio.h +++ b/panda/src/audiotraits/config_openalAudio.h @@ -26,5 +26,7 @@ extern "C" EXPCL_OPENAL_AUDIO void init_libOpenALAudio(); extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio(); extern ConfigVariableString openal_device; +extern ConfigVariableInt openal_buffer_delete_reattempts; +extern ConfigVariableDouble openal_buffer_delete_delay; #endif // CONFIG_OPENALAUDIO_H diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index cbdaadbfa1..bc254d4791 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -1052,7 +1052,7 @@ OpenALAudioManager::SoundData:: if (_sample != 0) { if (_manager->_is_valid) { _manager->make_current(); - alDeleteBuffers(1,&_sample); + _manager->delete_buffer(_sample); } _sample = 0; } @@ -1128,3 +1128,42 @@ discard_excess_cache(int sample_limit) { delete sd; } } + +/** + * Deletes an OpenAL buffer. This is a special function because some + * implementations of OpenAL (e.g. Apple's) don't unlock the buffers + * immediately, due to needing to coordinate with another thread. If this is + * the case, the alDeleteBuffers call will error back with AL_INVALID_OPERATION + * as if trying to delete an actively-used buffer, which will tell us to wait a + * bit and try again. + */ +void OpenALAudioManager:: +delete_buffer(ALuint buffer) { + ReMutexHolder holder(_lock); + int attempt = 0; + ALuint error; + + // Keep trying until we succeed (or give up). + while (true) { + alDeleteBuffers(1, &buffer); + error = alGetError(); + + if (error == AL_NO_ERROR) { + // Success! This will happen right away 99% of the time. + return; + } else if (error != AL_INVALID_OPERATION) { + // We weren't expecting that. This should be reported. + break; + } else if (attempt >= openal_buffer_delete_reattempts.get_value()) { + // We ran out of reattempts. Give up. + break; + } else { + // Make another attempt after (delay * 2^n) seconds. + Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << attempt)); + attempt++; + } + } + + // If we got here, one of the breaks above happened, indicating an error. + audio_error("failed to delete a buffer: " << alGetString(error) ); +} diff --git a/panda/src/audiotraits/openalAudioManager.h b/panda/src/audiotraits/openalAudioManager.h index 5917900869..1e16e4000b 100644 --- a/panda/src/audiotraits/openalAudioManager.h +++ b/panda/src/audiotraits/openalAudioManager.h @@ -129,6 +129,8 @@ private: void decrement_client_count(SoundData *sd); void discard_excess_cache(int limit); + void delete_buffer(ALuint buffer); + void starting_sound(OpenALAudioSound* audio); void stopping_sound(OpenALAudioSound* audio); diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index da04514e8b..86fc6e2bef 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -208,8 +208,7 @@ stop() { for (int i=0; i<((int)(_stream_queued.size())); i++) { ALuint buffer = _stream_queued[i]._buffer; if (buffer != _sd->_sample) { - alDeleteBuffers(1, &buffer); - al_audio_errcheck("deleting a buffer"); + _manager->delete_buffer(buffer); } } _stream_queued.resize(0); @@ -472,7 +471,7 @@ pull_used_buffers() { correct_calibrated_clock(rtc, al); } if (buffer != _sd->_sample) { - alDeleteBuffers(1,&buffer); + _manager->delete_buffer(buffer); } } } else { From fd5ce687b3f4b8bc7ba938dd78ea5874d167ce1c Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 1 Mar 2018 20:37:39 -0700 Subject: [PATCH 11/32] openal: Add cleanup guards to buffer functions Without this, the audio might encounter an error, call cleanup() on itself, and (if in the middle of update()) try to dereference its recently cleaned-up _sd pointer. Fixes #230 --- panda/src/audiotraits/openalAudioSound.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 86fc6e2bef..5922239a00 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -434,6 +434,7 @@ correct_calibrated_clock(double rtc, double t) { void OpenALAudioSound:: pull_used_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); + if (_manager == 0) return; while (_stream_queued.size()) { ALuint buffer = 0; ALint num_buffers = 0; @@ -489,6 +490,8 @@ push_fresh_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); static unsigned char data[65536]; + if (_manager == 0) return; + if (_sd->_sample) { while ((_loops_completed < _playing_loops) && (_stream_queued.size() < 100)) { From 1a6fb5300b296a815b9faeba9423184cffc16bd7 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 1 Mar 2018 21:09:32 -0700 Subject: [PATCH 12/32] openal: Be explicit about what constructs like `_sound != 0` mean --- panda/src/audiotraits/openalAudioSound.I | 30 ++++++++++++++++ panda/src/audiotraits/openalAudioSound.cxx | 42 +++++++++++----------- panda/src/audiotraits/openalAudioSound.h | 6 ++-- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/panda/src/audiotraits/openalAudioSound.I b/panda/src/audiotraits/openalAudioSound.I index 1b7e56a82c..6263872e1a 100644 --- a/panda/src/audiotraits/openalAudioSound.I +++ b/panda/src/audiotraits/openalAudioSound.I @@ -63,3 +63,33 @@ release_sound_data() { _sd = 0; } } + +/** + * Checks if the sound has NOT been cleaned up yet. + */ +bool OpenALAudioSound:: +is_valid() const { + return _manager != NULL; +} + +/** + * Checks if the sound is playing. This is per the OpenALAudioManager's + * definition of "playing" -- as in, "will be called upon every update" + * + * This is mainly intended for use in asserts. + */ +bool OpenALAudioSound:: +is_playing() const { + // Manager only gives us a _source if we need it (to talk to OpenAL), so: + return _source != 0; +} + +/** + * Checks if the sound has its SoundData structure open at the moment. + * + * This is mainly intended for use in asserts. + */ +bool OpenALAudioSound:: +has_sound_data() const { + return _sd != 0; +} diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 5922239a00..a79060cfb3 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -99,13 +99,13 @@ OpenALAudioSound:: void OpenALAudioSound:: cleanup() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) { + if (!is_valid()) { return; } - if (_source) { + if (is_playing()) { stop(); } - if (_sd) { + if (has_sound_data()) { _manager->decrement_client_count(_sd); _sd = 0; } @@ -119,7 +119,7 @@ cleanup() { void OpenALAudioSound:: play() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) return; + if (!is_valid()) return; PN_stdfloat px,py,pz,vx,vy,vz; @@ -136,7 +136,7 @@ play() { } _manager->starting_sound(this); - if (!_source) { + if (!is_playing()) { return; } @@ -195,9 +195,9 @@ play() { void OpenALAudioSound:: stop() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager==0) return; + if (!is_valid()) return; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -254,7 +254,7 @@ get_loop() const { void OpenALAudioSound:: set_loop_count(unsigned long loop_count) { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager==0) return; + if (!is_valid()) return; if (loop_count >= 1000000000) { loop_count = 0; @@ -434,7 +434,7 @@ correct_calibrated_clock(double rtc, double t) { void OpenALAudioSound:: pull_used_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) return; + if (!is_valid()) return; while (_stream_queued.size()) { ALuint buffer = 0; ALint num_buffers = 0; @@ -490,7 +490,7 @@ push_fresh_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); static unsigned char data[65536]; - if (_manager == 0) return; + if (!is_valid()) return; if (_sd->_sample) { while ((_loops_completed < _playing_loops) && @@ -517,9 +517,9 @@ push_fresh_buffers() { break; } ALuint buffer = make_buffer(samples, channels, rate, data); - if (_manager == 0) return; + if (!is_valid()) return; queue_buffer(buffer, samples, loop_index, time_offset); - if (_manager == 0) return; + if (!is_valid()) return; fill += samples; } } @@ -541,7 +541,7 @@ set_time(PN_stdfloat time) { PN_stdfloat OpenALAudioSound:: get_time() const { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_manager == 0) { + if (!is_valid()) { return 0.0; } return _current_time; @@ -553,7 +553,7 @@ get_time() const { void OpenALAudioSound:: cache_time(double rtc) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(_source != 0); + assert(is_playing()); double t=get_calibrated_clock(rtc); double max = _length * _playing_loops; if (t >= max) { @@ -571,7 +571,7 @@ set_volume(PN_stdfloat volume) { ReMutexHolder holder(OpenALAudioManager::_lock); _volume=volume; - if (_source) { + if (is_playing()) { volume*=_manager->get_volume(); _manager->make_current(); alGetError(); // clear errors @@ -615,7 +615,7 @@ void OpenALAudioSound:: set_play_rate(PN_stdfloat play_rate) { ReMutexHolder holder(OpenALAudioManager::_lock); _play_rate = play_rate; - if (_source) { + if (is_playing()) { alSourcef(_source, AL_PITCH, play_rate); } } @@ -657,7 +657,7 @@ set_3d_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx _velocity[1] = vz; _velocity[2] = -vy; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -693,7 +693,7 @@ set_3d_min_distance(PN_stdfloat dist) { ReMutexHolder holder(OpenALAudioManager::_lock); _min_dist = dist; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -718,7 +718,7 @@ set_3d_max_distance(PN_stdfloat dist) { ReMutexHolder holder(OpenALAudioManager::_lock); _max_dist = dist; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -743,7 +743,7 @@ set_3d_drop_off_factor(PN_stdfloat factor) { ReMutexHolder holder(OpenALAudioManager::_lock); _drop_off_factor = factor; - if (_source) { + if (is_playing()) { _manager->make_current(); alGetError(); // clear errors @@ -831,7 +831,7 @@ get_name() const { AudioSound::SoundStatus OpenALAudioSound:: status() const { ReMutexHolder holder(OpenALAudioManager::_lock); - if (_source==0) { + if (!is_playing()) { return AudioSound::READY; } if ((_loops_completed >= _playing_loops)&&(_stream_queued.size()==0)) { diff --git a/panda/src/audiotraits/openalAudioSound.h b/panda/src/audiotraits/openalAudioSound.h index f5b02667e5..b44a889445 100644 --- a/panda/src/audiotraits/openalAudioSound.h +++ b/panda/src/audiotraits/openalAudioSound.h @@ -119,9 +119,11 @@ private: INLINE bool require_sound_data(); INLINE void release_sound_data(); -private: + INLINE bool is_valid() const; + INLINE bool is_playing() const; + INLINE bool has_sound_data() const; - void do_stop(); +private: PT(MovieAudio) _movie; OpenALAudioManager::SoundData *_sd; From 9a5d7d82544f9f263c36d0ee09c8693cb74d55c4 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 1 Mar 2018 21:27:00 -0700 Subject: [PATCH 13/32] openal: Add several asserts This also includes a few slight style fixes. --- panda/src/audiotraits/openalAudioSound.cxx | 36 +++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index a79060cfb3..d257fefd72 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -119,6 +119,7 @@ cleanup() { void OpenALAudioSound:: play() { ReMutexHolder holder(OpenALAudioManager::_lock); + if (!is_valid()) return; PN_stdfloat px,py,pz,vx,vy,vz; @@ -195,11 +196,14 @@ play() { void OpenALAudioSound:: stop() { ReMutexHolder holder(OpenALAudioManager::_lock); + if (!is_valid()) return; if (is_playing()) { _manager->make_current(); + assert(has_sound_data()); + alGetError(); // clear errors alSourceStop(_source); al_audio_errcheck("stopping a source"); @@ -224,6 +228,9 @@ stop() { void OpenALAudioSound:: finished() { ReMutexHolder holder(OpenALAudioManager::_lock); + + if (!is_valid()) return; + stop(); _current_time = _length; if (!_finished_event.empty()) { @@ -254,6 +261,7 @@ get_loop() const { void OpenALAudioSound:: set_loop_count(unsigned long loop_count) { ReMutexHolder holder(OpenALAudioManager::_lock); + if (!is_valid()) return; if (loop_count >= 1000000000) { @@ -281,9 +289,14 @@ void OpenALAudioSound:: restart_stalled_audio() { ReMutexHolder holder(OpenALAudioManager::_lock); ALenum status; + + if (!is_valid()) return; + assert(is_playing()); + if (_stream_queued.size() == 0) { return; } + alGetError(); alGetSourcei(_source, AL_SOURCE_STATE, &status); if (status != AL_PLAYING) { @@ -297,6 +310,9 @@ restart_stalled_audio() { void OpenALAudioSound:: queue_buffer(ALuint buffer, int samples, int loop_index, double time_offset) { ReMutexHolder holder(OpenALAudioManager::_lock); + + assert(is_playing()); + // Now push the buffer into the stream queue. alGetError(); alSourceQueueBuffers(_source,1,&buffer); @@ -321,6 +337,8 @@ ALuint OpenALAudioSound:: make_buffer(int samples, int channels, int rate, unsigned char *data) { ReMutexHolder holder(OpenALAudioManager::_lock); + assert(is_playing()); + // Allocate a buffer to hold the data. alGetError(); ALuint buffer; @@ -353,6 +371,8 @@ int OpenALAudioSound:: read_stream_data(int bytelen, unsigned char *buffer) { ReMutexHolder holder(OpenALAudioManager::_lock); + assert(has_sound_data()); + MovieAudioCursor *cursor = _sd->_stream; double length = cursor->length(); int channels = cursor->audio_channels(); @@ -403,6 +423,9 @@ read_stream_data(int bytelen, unsigned char *buffer) { void OpenALAudioSound:: correct_calibrated_clock(double rtc, double t) { ReMutexHolder holder(OpenALAudioManager::_lock); + + assert(is_playing()); + double cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale; double diff = cc-t; _calibrated_clock_decavg = (_calibrated_clock_decavg * 0.95) + (diff * 0.05); @@ -434,7 +457,11 @@ correct_calibrated_clock(double rtc, double t) { void OpenALAudioSound:: pull_used_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); + if (!is_valid()) return; + assert(is_playing()); + assert(has_sound_data()); + while (_stream_queued.size()) { ALuint buffer = 0; ALint num_buffers = 0; @@ -491,6 +518,8 @@ push_fresh_buffers() { static unsigned char data[65536]; if (!is_valid()) return; + assert(is_playing()); + assert(has_sound_data()); if (_sd->_sample) { while ((_loops_completed < _playing_loops) && @@ -553,7 +582,9 @@ get_time() const { void OpenALAudioSound:: cache_time(double rtc) { ReMutexHolder holder(OpenALAudioManager::_lock); + assert(is_playing()); + double t=get_calibrated_clock(rtc); double max = _length * _playing_loops; if (t >= max) { @@ -761,13 +792,16 @@ get_3d_drop_off_factor() const { } /** - * Sets whether the sound is marked "active". By default, the active flag + * Sets whether the sound is marked "active". By default, the active flag is * true for all sounds. If the active flag is set to false for any particular * sound, the sound will not be heard. */ void OpenALAudioSound:: set_active(bool active) { ReMutexHolder holder(OpenALAudioManager::_lock); + + if (!is_valid()) return; + if (_active!=active) { _active=active; if (_active) { From a10cd7d8bb454544fb3800db4dec451fb6d5123c Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 2 Mar 2018 00:53:10 -0700 Subject: [PATCH 14/32] openal: Always use release_sound_data This simplifies cleanup() a little bit. --- panda/src/audiotraits/openalAudioSound.I | 11 +++++++++-- panda/src/audiotraits/openalAudioSound.cxx | 7 +++---- panda/src/audiotraits/openalAudioSound.h | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/panda/src/audiotraits/openalAudioSound.I b/panda/src/audiotraits/openalAudioSound.I index 6263872e1a..03592adfde 100644 --- a/panda/src/audiotraits/openalAudioSound.I +++ b/panda/src/audiotraits/openalAudioSound.I @@ -55,10 +55,17 @@ require_sound_data() { /** * Checks if the sound data record is present and releasable, and if so, * releases it. + * + * The sound data is "releasable" if it's from an ordinary, local file. Remote + * streams cannot necessarily be reopened if lost, so we'll hold onto them if + * so. The `force` argument overrides this, indicating we don't intend to + * reacquire the sound data. */ void OpenALAudioSound:: -release_sound_data() { - if ((_sd!=0) && (!_movie->get_filename().empty())) { +release_sound_data(bool force) { + if (!has_sound_data()) return; + + if (force || !_movie->get_filename().empty()) { _manager->decrement_client_count(_sd); _sd = 0; } diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index d257fefd72..628a3a2279 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -80,7 +80,7 @@ OpenALAudioSound(OpenALAudioManager* manager, audio_warning("stereo sound " << movie->get_filename() << " will not be spatialized"); } } - release_sound_data(); + release_sound_data(false); } @@ -106,8 +106,7 @@ cleanup() { stop(); } if (has_sound_data()) { - _manager->decrement_client_count(_sd); - _sd = 0; + release_sound_data(true); } _manager->release_sound(this); _manager = 0; @@ -219,7 +218,7 @@ stop() { } _manager->stopping_sound(this); - release_sound_data(); + release_sound_data(false); } /** diff --git a/panda/src/audiotraits/openalAudioSound.h b/panda/src/audiotraits/openalAudioSound.h index b44a889445..e10f4f179a 100644 --- a/panda/src/audiotraits/openalAudioSound.h +++ b/panda/src/audiotraits/openalAudioSound.h @@ -117,7 +117,7 @@ private: void pull_used_buffers(); void push_fresh_buffers(); INLINE bool require_sound_data(); - INLINE void release_sound_data(); + INLINE void release_sound_data(bool force); INLINE bool is_valid() const; INLINE bool is_playing() const; From cb85d01de60579c193571080a1792abe91de59b5 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 2 Mar 2018 13:39:31 -0700 Subject: [PATCH 15/32] openal: Always use INLINE in .I files --- panda/src/audiotraits/openalAudioSound.I | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/panda/src/audiotraits/openalAudioSound.I b/panda/src/audiotraits/openalAudioSound.I index 03592adfde..8a60ece6e5 100644 --- a/panda/src/audiotraits/openalAudioSound.I +++ b/panda/src/audiotraits/openalAudioSound.I @@ -40,7 +40,7 @@ get_calibrated_clock(double rtc) const { * * Returns true on success, false on failure. */ -bool OpenALAudioSound:: +INLINE bool OpenALAudioSound:: require_sound_data() { if (_sd==0) { _sd = _manager->get_sound_data(_movie, _desired_mode); @@ -61,7 +61,7 @@ require_sound_data() { * so. The `force` argument overrides this, indicating we don't intend to * reacquire the sound data. */ -void OpenALAudioSound:: +INLINE void OpenALAudioSound:: release_sound_data(bool force) { if (!has_sound_data()) return; @@ -74,7 +74,7 @@ release_sound_data(bool force) { /** * Checks if the sound has NOT been cleaned up yet. */ -bool OpenALAudioSound:: +INLINE bool OpenALAudioSound:: is_valid() const { return _manager != NULL; } @@ -85,7 +85,7 @@ is_valid() const { * * This is mainly intended for use in asserts. */ -bool OpenALAudioSound:: +INLINE bool OpenALAudioSound:: is_playing() const { // Manager only gives us a _source if we need it (to talk to OpenAL), so: return _source != 0; @@ -96,7 +96,7 @@ is_playing() const { * * This is mainly intended for use in asserts. */ -bool OpenALAudioSound:: +INLINE bool OpenALAudioSound:: has_sound_data() const { return _sd != 0; } From c89bb3d030ac1a87b3ed95e36073d2a426cdac4e Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 2 Mar 2018 13:41:44 -0700 Subject: [PATCH 16/32] openal: "reattempt" -> "retry" --- panda/src/audiotraits/config_openalAudio.cxx | 16 ++++++++-------- panda/src/audiotraits/config_openalAudio.h | 2 +- panda/src/audiotraits/openalAudioManager.cxx | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/panda/src/audiotraits/config_openalAudio.cxx b/panda/src/audiotraits/config_openalAudio.cxx index 36803e7d04..a3340477c2 100644 --- a/panda/src/audiotraits/config_openalAudio.cxx +++ b/panda/src/audiotraits/config_openalAudio.cxx @@ -30,21 +30,21 @@ ConfigVariableString openal_device PRC_DESC("Specify the OpenAL device string for audio playback (no quotes). If this " "is not specified, the OpenAL default device is used.")); -ConfigVariableInt openal_buffer_delete_reattempts -("openal-buffer-delete-reattempts", 5, +ConfigVariableInt openal_buffer_delete_retries +("openal-buffer-delete-retries", 5, PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " - "sound plugin will wait a moment and reattempt deletion, with an " - "exponentially-increasing delay for each attempt. This number " - "specifies how many repeat attempts (not counting the initial attempt) " + "sound plugin will wait a moment and retry deletion, with an " + "exponentially-increasing delay for each try. This number " + "specifies how many repeat tries (not counting the initial try) " "should be made before giving up and raising an error.")); ConfigVariableDouble openal_buffer_delete_delay ("openal-buffer-delete-delay", 0.001, PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL " - "sound plugin will wait a moment and reattempt deletion, with an " - "exponentially-increasing delay for each attempt. This number " + "sound plugin will wait a moment and retry deletion, with an " + "exponentially-increasing delay for each try. This number " "specifies how long, in seconds, the OpenAL plugin will wait after " - "its first failed attempt. The second attempt will be double this " + "its first failed try. The second try will be double this " "delay, the third quadruple, and so on.")); diff --git a/panda/src/audiotraits/config_openalAudio.h b/panda/src/audiotraits/config_openalAudio.h index 96d3429519..6817362e31 100644 --- a/panda/src/audiotraits/config_openalAudio.h +++ b/panda/src/audiotraits/config_openalAudio.h @@ -26,7 +26,7 @@ extern "C" EXPCL_OPENAL_AUDIO void init_libOpenALAudio(); extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio(); extern ConfigVariableString openal_device; -extern ConfigVariableInt openal_buffer_delete_reattempts; +extern ConfigVariableInt openal_buffer_delete_retries; extern ConfigVariableDouble openal_buffer_delete_delay; #endif // CONFIG_OPENALAUDIO_H diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index bc254d4791..f093aae8b6 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -1140,7 +1140,7 @@ discard_excess_cache(int sample_limit) { void OpenALAudioManager:: delete_buffer(ALuint buffer) { ReMutexHolder holder(_lock); - int attempt = 0; + int tries = 0; ALuint error; // Keep trying until we succeed (or give up). @@ -1154,13 +1154,13 @@ delete_buffer(ALuint buffer) { } else if (error != AL_INVALID_OPERATION) { // We weren't expecting that. This should be reported. break; - } else if (attempt >= openal_buffer_delete_reattempts.get_value()) { - // We ran out of reattempts. Give up. + } else if (tries >= openal_buffer_delete_retries.get_value()) { + // We ran out of retries. Give up. break; } else { - // Make another attempt after (delay * 2^n) seconds. - Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << attempt)); - attempt++; + // Make another try after (delay * 2^n) seconds. + Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << tries)); + tries++; } } From 99dc462174382f7065751eb7ea44d0c8fdf16789 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 2 Mar 2018 13:55:03 -0700 Subject: [PATCH 17/32] openal: assert -> nassert --- panda/src/audiotraits/openalAudioManager.cxx | 12 +++++----- panda/src/audiotraits/openalAudioSound.cxx | 24 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index f093aae8b6..2ba02c2fa7 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -524,7 +524,7 @@ get_sound(const string &file_name, bool positional, int mode) { void OpenALAudioManager:: uncache_sound(const string& file_name) { ReMutexHolder holder(_lock); - assert(is_valid()); + nassertv(is_valid()); Filename path = file_name; VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); @@ -910,7 +910,7 @@ reduce_sounds_playing_to(unsigned int count) { int limit = _sounds_playing.size() - count; while (limit-- > 0) { SoundsPlaying::iterator sound = _sounds_playing.begin(); - assert(sound != _sounds_playing.end()); + nassertv(sound != _sounds_playing.end()); // When the user stops a sound, there is still a PT in the user's hand. // When we stop a sound here, however, this can remove the last PT. This // can cause an ugly recursion where stop calls the destructor, and the @@ -1111,8 +1111,8 @@ discard_excess_cache(int sample_limit) { while (((int)_expiring_samples.size()) > sample_limit) { SoundData *sd = (SoundData*)(_expiring_samples.front()); - assert(sd->_client_count == 0); - assert(sd->_expire == _expiring_samples.begin()); + nassertv(sd->_client_count == 0); + nassertv(sd->_expire == _expiring_samples.begin()); _expiring_samples.pop_front(); _sample_cache.erase(_sample_cache.find(sd->_movie->get_filename())); audio_debug("Expiring: " << sd->_movie->get_filename().get_basename()); @@ -1121,8 +1121,8 @@ discard_excess_cache(int sample_limit) { while (((int)_expiring_streams.size()) > stream_limit) { SoundData *sd = (SoundData*)(_expiring_streams.front()); - assert(sd->_client_count == 0); - assert(sd->_expire == _expiring_streams.begin()); + nassertv(sd->_client_count == 0); + nassertv(sd->_expire == _expiring_streams.begin()); _expiring_streams.pop_front(); audio_debug("Expiring: " << sd->_movie->get_filename().get_basename()); delete sd; diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 628a3a2279..faa9df5717 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -201,7 +201,7 @@ stop() { if (is_playing()) { _manager->make_current(); - assert(has_sound_data()); + nassertv(has_sound_data()); alGetError(); // clear errors alSourceStop(_source); @@ -290,7 +290,7 @@ restart_stalled_audio() { ALenum status; if (!is_valid()) return; - assert(is_playing()); + nassertv(is_playing()); if (_stream_queued.size() == 0) { return; @@ -310,7 +310,7 @@ void OpenALAudioSound:: queue_buffer(ALuint buffer, int samples, int loop_index, double time_offset) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(is_playing()); + nassertv(is_playing()); // Now push the buffer into the stream queue. alGetError(); @@ -336,7 +336,7 @@ ALuint OpenALAudioSound:: make_buffer(int samples, int channels, int rate, unsigned char *data) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(is_playing()); + nassertr(is_playing(), 0); // Allocate a buffer to hold the data. alGetError(); @@ -370,7 +370,7 @@ int OpenALAudioSound:: read_stream_data(int bytelen, unsigned char *buffer) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(has_sound_data()); + nassertr(has_sound_data(), 0); MovieAudioCursor *cursor = _sd->_stream; double length = cursor->length(); @@ -423,7 +423,7 @@ void OpenALAudioSound:: correct_calibrated_clock(double rtc, double t) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(is_playing()); + nassertv(is_playing()); double cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale; double diff = cc-t; @@ -458,8 +458,8 @@ pull_used_buffers() { ReMutexHolder holder(OpenALAudioManager::_lock); if (!is_valid()) return; - assert(is_playing()); - assert(has_sound_data()); + nassertv(is_playing()); + nassertv(has_sound_data()); while (_stream_queued.size()) { ALuint buffer = 0; @@ -517,8 +517,8 @@ push_fresh_buffers() { static unsigned char data[65536]; if (!is_valid()) return; - assert(is_playing()); - assert(has_sound_data()); + nassertv(is_playing()); + nassertv(has_sound_data()); if (_sd->_sample) { while ((_loops_completed < _playing_loops) && @@ -545,7 +545,7 @@ push_fresh_buffers() { break; } ALuint buffer = make_buffer(samples, channels, rate, data); - if (!is_valid()) return; + if (!is_valid() || !buffer) return; queue_buffer(buffer, samples, loop_index, time_offset); if (!is_valid()) return; fill += samples; @@ -582,7 +582,7 @@ void OpenALAudioSound:: cache_time(double rtc) { ReMutexHolder holder(OpenALAudioManager::_lock); - assert(is_playing()); + nassertv(is_playing()); double t=get_calibrated_clock(rtc); double max = _length * _playing_loops; From d8b48a3837d26aa5c9c6373eaf3b5750bfdd707e Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Tue, 6 Mar 2018 19:01:34 -0700 Subject: [PATCH 18/32] openal: `ptr != 0` -> `ptr != NULL` --- panda/src/audiotraits/openalAudioSound.I | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/audiotraits/openalAudioSound.I b/panda/src/audiotraits/openalAudioSound.I index 8a60ece6e5..9a090adc3d 100644 --- a/panda/src/audiotraits/openalAudioSound.I +++ b/panda/src/audiotraits/openalAudioSound.I @@ -98,5 +98,5 @@ is_playing() const { */ INLINE bool OpenALAudioSound:: has_sound_data() const { - return _sd != 0; + return _sd != NULL; } From 9a3147874425c5677e34c6aec272d0cc7342c542 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 11:19:43 +0100 Subject: [PATCH 19/32] task: remove accidentally committed debug message --- panda/src/event/asyncFuture.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/panda/src/event/asyncFuture.cxx b/panda/src/event/asyncFuture.cxx index f1aaaba842..25ae6bfa1c 100644 --- a/panda/src/event/asyncFuture.cxx +++ b/panda/src/event/asyncFuture.cxx @@ -244,7 +244,6 @@ add_waiting_task(AsyncTask *task) { */ void AsyncFuture:: wake_task(AsyncTask *task) { - cerr << "waking task\n"; AsyncTaskManager *manager = task->_manager; if (manager == nullptr) { // If it's an unscheduled task, schedule it on the same manager as the From b0b32b9d6a1a9b367011ccf3fe892f14f11028f0 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 11:21:14 +0100 Subject: [PATCH 20/32] direct: fix Python 3 support in Pmw-based tools Fixes #276 --- direct/src/showbase/TkGlobal.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/direct/src/showbase/TkGlobal.py b/direct/src/showbase/TkGlobal.py index bfdeb9b48f..673d337e3c 100644 --- a/direct/src/showbase/TkGlobal.py +++ b/direct/src/showbase/TkGlobal.py @@ -12,5 +12,28 @@ else: if '_Pmw' in sys.modules: sys.modules['_Pmw'].__name__ = '_Pmw' +# Hack to workaround broken Pmw.NoteBook in Python 3 +def bordercolors(root, colorName): + lightRGB = [] + darkRGB = [] + for value in Pmw.Color.name2rgb(root, colorName, 1): + value40pc = (14 * value) // 10 + if value40pc > int(Pmw.Color._MAX_RGB): + value40pc = int(Pmw.Color._MAX_RGB) + valueHalfWhite = (int(Pmw.Color._MAX_RGB) + value) // 2; + lightRGB.append(max(value40pc, valueHalfWhite)) + + darkValue = (60 * value) // 100 + darkRGB.append(darkValue) + + return ( + '#%04x%04x%04x' % (lightRGB[0], lightRGB[1], lightRGB[2]), + '#%04x%04x%04x' % (darkRGB[0], darkRGB[1], darkRGB[2]) + ) + +Pmw.Color.bordercolors = bordercolors +del bordercolors + + def spawnTkLoop(): base.spawnTkLoop() From 15f6ed1ba297391e720f4b271101fd64010a18ad Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 11:38:52 +0100 Subject: [PATCH 21/32] androiddisplay: remove error messages leftover from debugging --- panda/src/androiddisplay/androidGraphicsWindow.cxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/panda/src/androiddisplay/androidGraphicsWindow.cxx b/panda/src/androiddisplay/androidGraphicsWindow.cxx index 31cd4e23d6..1a91f21186 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.cxx +++ b/panda/src/androiddisplay/androidGraphicsWindow.cxx @@ -309,8 +309,6 @@ open_window() { _fb_properties = androidgsg->get_fb_properties(); - androiddisplay_cat.error() << "open_window done\n"; - return true; } @@ -366,7 +364,6 @@ create_surface() { // Create a context. if (androidgsg->_context == EGL_NO_CONTEXT) { - androiddisplay_cat.error() << "creating context\n"; if (!androidgsg->create_context()) { return false; } From bfff7e10008930eafe92b974c0f679ff17e29d29 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 11:41:00 +0100 Subject: [PATCH 22/32] tests: don't assert if pipe cannot create physical windows --- tests/display/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/display/conftest.py b/tests/display/conftest.py index c66ef2f54a..88a2b5f3c3 100644 --- a/tests/display/conftest.py +++ b/tests/display/conftest.py @@ -41,7 +41,9 @@ def window(graphics_pipe, graphics_engine): ) graphics_engine.open_windows() - assert win is not None + if win is None: + pytest.skip("GraphicsPipe cannot make windows") + yield win if win is not None: From b10ee32752f0568a3d1188ef59bb0da871327228 Mon Sep 17 00:00:00 2001 From: Michael Wass Date: Thu, 8 Mar 2018 11:43:47 +0100 Subject: [PATCH 23/32] direct: Fix some more NameErrors Closes #274 --- direct/src/distributed/PyDatagramIterator.py | 6 +----- direct/src/stdpy/threading.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/direct/src/distributed/PyDatagramIterator.py b/direct/src/distributed/PyDatagramIterator.py index e97600f6b4..60267a1ab9 100755 --- a/direct/src/distributed/PyDatagramIterator.py +++ b/direct/src/distributed/PyDatagramIterator.py @@ -75,7 +75,7 @@ class PyDatagramIterator(DatagramIterator): b = self.getUint8() retVal.append((a, b)) else: - raise Exception("Error: No such type as: " + str(subAtomicType)) + raise Exception("Error: No such type as: " + str(subatomicType)) else: # See if it is in the handy dict getFunc = self.FuncDict.get(subatomicType) @@ -121,8 +121,4 @@ class PyDatagramIterator(DatagramIterator): else: raise Exception("Error: No such type as: " + str(subatomicType)) - - return retVal - - diff --git a/direct/src/stdpy/threading.py b/direct/src/stdpy/threading.py index b4cb6d9228..466a198a3a 100644 --- a/direct/src/stdpy/threading.py +++ b/direct/src/stdpy/threading.py @@ -302,7 +302,7 @@ class BoundedSemaphore(Semaphore): Semaphore.__init__(value) def release(self): - if self.getCount() > value: + if self.getCount() > self.__max: raise ValueError Semaphore.release(self) From 5d2110c6442b1b0ba5a124153085c5e3c519660d Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 12:19:22 +0100 Subject: [PATCH 24/32] bullet: add force_update_all_aabbs property to BulletWorld --- panda/src/bullet/bulletWorld.cxx | 18 ++++++++++++++++++ panda/src/bullet/bulletWorld.h | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/panda/src/bullet/bulletWorld.cxx b/panda/src/bullet/bulletWorld.cxx index f3d48e3c74..18c23c8896 100644 --- a/panda/src/bullet/bulletWorld.cxx +++ b/panda/src/bullet/bulletWorld.cxx @@ -1104,6 +1104,24 @@ get_group_collision_flag(unsigned int group1, unsigned int group2) const { return _filter_cb2._collide[group1].get_bit(group2); } +/** + * + */ +void BulletWorld:: +set_force_update_all_aabbs(bool force) { + LightMutexHolder holder(get_global_lock()); + _world->setForceUpdateAllAabbs(force); +} + +/** + * + */ +bool BulletWorld:: +get_force_update_all_aabbs() const { + LightMutexHolder holder(get_global_lock()); + return _world->getForceUpdateAllAabbs(); +} + /** * */ diff --git a/panda/src/bullet/bulletWorld.h b/panda/src/bullet/bulletWorld.h index 021b6ee268..36f3723535 100644 --- a/panda/src/bullet/bulletWorld.h +++ b/panda/src/bullet/bulletWorld.h @@ -134,6 +134,9 @@ PUBLISHED: void set_group_collision_flag(unsigned int group1, unsigned int group2, bool enable); bool get_group_collision_flag(unsigned int group1, unsigned int group2) const; + void set_force_update_all_aabbs(bool force); + bool get_force_update_all_aabbs() const; + // Callbacks void set_contact_added_callback(CallbackObject *obj); void clear_contact_added_callback(); @@ -166,6 +169,8 @@ PUBLISHED: MAKE_SEQ_PROPERTY(vehicles, get_num_vehicles, get_vehicle); MAKE_SEQ_PROPERTY(constraints, get_num_constraints, get_constraint); MAKE_SEQ_PROPERTY(manifolds, get_num_manifolds, get_manifold); + MAKE_PROPERTY(force_update_all_aabbs, get_force_update_all_aabbs, + set_force_update_all_aabbs); PUBLISHED: // Deprecated methods, will be removed soon void attach_ghost(BulletGhostNode *node); From 766b38fb7a25db6f6dc13261c051dd572ed671b1 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Mar 2018 12:58:16 +0100 Subject: [PATCH 25/32] dtoolbase: make TypeHandle a constexpr class --- dtool/src/dtoolbase/typeHandle.I | 18 ++++++++++++------ dtool/src/dtoolbase/typeHandle.h | 11 ++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/dtool/src/dtoolbase/typeHandle.I b/dtool/src/dtoolbase/typeHandle.I index e002fbb1d7..3e9e53a9ae 100644 --- a/dtool/src/dtoolbase/typeHandle.I +++ b/dtool/src/dtoolbase/typeHandle.I @@ -194,9 +194,9 @@ output(ostream &out) const { /** * Returns a special zero-valued TypeHandle that is used to indicate no type. */ -INLINE TypeHandle TypeHandle:: +CONSTEXPR TypeHandle TypeHandle:: none() { - return _none; + return TypeHandle(0); } /** @@ -213,9 +213,15 @@ operator bool () const { * * See TypeRegistry::find_type_by_id(). */ -INLINE TypeHandle TypeHandle:: +CONSTEXPR TypeHandle TypeHandle:: from_index(int index) { - TypeHandle handle; - handle._index = index; - return handle; + return TypeHandle(index); +} + +/** + * Private constructor for initializing a TypeHandle from an index, used by + * none() and by from_index(). + */ +CONSTEXPR TypeHandle:: +TypeHandle(int index) : _index(index) { } diff --git a/dtool/src/dtoolbase/typeHandle.h b/dtool/src/dtoolbase/typeHandle.h index f770b1810b..58e69646f4 100644 --- a/dtool/src/dtoolbase/typeHandle.h +++ b/dtool/src/dtoolbase/typeHandle.h @@ -80,6 +80,8 @@ class TypedObject; */ class EXPCL_DTOOL TypeHandle FINAL { PUBLISHED: + TypeHandle() NOEXCEPT DEFAULT_CTOR; + enum MemoryClass { MC_singleton, MC_array, @@ -127,7 +129,7 @@ PUBLISHED: INLINE int get_index() const; INLINE void output(ostream &out) const; - INLINE static TypeHandle none(); + CONSTEXPR static TypeHandle none(); INLINE operator bool () const; MAKE_PROPERTY(index, get_index); @@ -140,12 +142,15 @@ public: void *reallocate_array(void *ptr, size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT); void deallocate_array(void *ptr); - INLINE static TypeHandle from_index(int index); + CONSTEXPR static TypeHandle from_index(int index); private: - int _index; + CONSTEXPR TypeHandle(int index); + + // Only kept temporarily for ABI compatibility. static TypeHandle _none; + int _index; friend class TypeRegistry; }; From 280b13a289dfe4cda226a3828c6e2301a7368fc1 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 8 Mar 2018 13:42:56 -0700 Subject: [PATCH 26/32] char: Remove references to dead "ComputedVertices" class This hasn't been a thing for nearly 13 years. --- panda/src/char/character.h | 1 - panda/src/char/characterJoint.cxx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/panda/src/char/character.h b/panda/src/char/character.h index 1903d39ab8..80492df93c 100644 --- a/panda/src/char/character.h +++ b/panda/src/char/character.h @@ -30,7 +30,6 @@ #include "sliderTable.h" class CharacterJointBundle; -class ComputedVertices; /** * An animated character, with skeleton-morph animation and either soft- diff --git a/panda/src/char/characterJoint.cxx b/panda/src/char/characterJoint.cxx index 77b3a99e32..9efb002349 100644 --- a/panda/src/char/characterJoint.cxx +++ b/panda/src/char/characterJoint.cxx @@ -60,7 +60,7 @@ CharacterJoint(Character *character, // update_internals() to get our _net_transform set properly. update_internals(root, parent, true, false, current_thread); - // And then compute its inverse. This is needed for ComputedVertices, + // And then compute its inverse. This is needed for JointVertexTransform, // during animation. _initial_net_transform_inverse = invert(_net_transform); } From 5f14d9c48f250ff982a1369b1dce1bb8150bdefc Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 8 Mar 2018 14:27:04 -0700 Subject: [PATCH 27/32] char: Move JointVertexTransform::_matrix to CharacterJoint The rationale is that CharacterJoint should contain all of the joint state information, and JointVertexTransform should be pretty much devoid of state so that we don't have to worry about synchronizing it. JointVertexTransform::_matrix was just a cached/precomputed matrix that transforms from original vertex positions to animated vertex positions, so moving it to CharacterJoint doesn't change any engine functionality. This also removes the useless lock on recomputing that matrix. It was useless because it was computing from shared state in CharacterJoint that wasn't properly synchronized, but this would have to be fixed by making CharacterJoint pipeline-cycled - a lock won't do. --- panda/src/char/characterJoint.cxx | 18 ++++++++++----- panda/src/char/characterJoint.h | 6 +++++ panda/src/char/jointVertexTransform.I | 10 --------- panda/src/char/jointVertexTransform.cxx | 30 +++++-------------------- panda/src/char/jointVertexTransform.h | 7 ------ 5 files changed, 23 insertions(+), 48 deletions(-) diff --git a/panda/src/char/characterJoint.cxx b/panda/src/char/characterJoint.cxx index 9efb002349..6ce5b0be88 100644 --- a/panda/src/char/characterJoint.cxx +++ b/panda/src/char/characterJoint.cxx @@ -39,7 +39,8 @@ CharacterJoint(const CharacterJoint ©) : MovingPartMatrix(copy), _character(NULL), _net_transform(copy._net_transform), - _initial_net_transform_inverse(copy._initial_net_transform_inverse) + _initial_net_transform_inverse(copy._initial_net_transform_inverse), + _skinning_matrix(copy._skinning_matrix) { // We don't copy the sets of transform nodes. } @@ -60,9 +61,12 @@ CharacterJoint(Character *character, // update_internals() to get our _net_transform set properly. update_internals(root, parent, true, false, current_thread); - // And then compute its inverse. This is needed for JointVertexTransform, - // during animation. + // And then compute its inverse. This is needed to track changes in + // _net_transform as the joint moves, so we can recompute _skinning_matrix, + // which maps vertices from their initial positions to their animated + // positions. _initial_net_transform_inverse = invert(_net_transform); + _skinning_matrix = LMatrix4::ident_mat(); } /** @@ -141,11 +145,13 @@ update_internals(PartBundle *root, PartGroup *parent, bool self_changed, } } - // Also tell our related JointVertexTransforms that they now need to - // recompute themselves. + // Recompute the transform used by any vertices animated by this joint. + _skinning_matrix = _initial_net_transform_inverse * _net_transform; + + // Also tell our related JointVertexTransforms that we've changed their + // underlying matrix. VertexTransforms::iterator vti; for (vti = _vertex_transforms.begin(); vti != _vertex_transforms.end(); ++vti) { - (*vti)->_matrix_stale = true; (*vti)->mark_modified(current_thread); } } diff --git a/panda/src/char/characterJoint.h b/panda/src/char/characterJoint.h index 759d175c7e..5da83def66 100644 --- a/panda/src/char/characterJoint.h +++ b/panda/src/char/characterJoint.h @@ -108,6 +108,12 @@ public: LMatrix4 _net_transform; LMatrix4 _initial_net_transform_inverse; + // This is the product of the above; the matrix that gets applied to a + // vertex (whose coordinates are in the coordinate space of the character + // in its neutral pose) to transform it from its neutral position to its + // animated position. + LMatrix4 _skinning_matrix; + public: virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/char/jointVertexTransform.I b/panda/src/char/jointVertexTransform.I index a5a2a2e8cf..4973ae6d60 100644 --- a/panda/src/char/jointVertexTransform.I +++ b/panda/src/char/jointVertexTransform.I @@ -18,13 +18,3 @@ INLINE const CharacterJoint *JointVertexTransform:: get_joint() const { return _joint; } - -/** - * Recomputes _matrix if it needs it. - */ -INLINE void JointVertexTransform:: -check_matrix() const { - if (_matrix_stale) { - ((JointVertexTransform *)this)->compute_matrix(); - } -} diff --git a/panda/src/char/jointVertexTransform.cxx b/panda/src/char/jointVertexTransform.cxx index cf943669a7..5212aa6cd9 100644 --- a/panda/src/char/jointVertexTransform.cxx +++ b/panda/src/char/jointVertexTransform.cxx @@ -24,8 +24,7 @@ TypeHandle JointVertexTransform::_type_handle; * Constructs an invalid object; used only by the bam loader. */ JointVertexTransform:: -JointVertexTransform() : - _matrix_stale(true) +JointVertexTransform() { } @@ -35,8 +34,7 @@ JointVertexTransform() : */ JointVertexTransform:: JointVertexTransform(CharacterJoint *joint) : - _joint(joint), - _matrix_stale(true) + _joint(joint) { // Tell the joint that we need to be informed when it moves. _joint->_vertex_transforms.insert(this); @@ -57,8 +55,7 @@ JointVertexTransform:: */ void JointVertexTransform:: get_matrix(LMatrix4 &matrix) const { - check_matrix(); - matrix = _matrix; + matrix = _joint->_skinning_matrix; } /** @@ -69,8 +66,7 @@ get_matrix(LMatrix4 &matrix) const { */ void JointVertexTransform:: mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const { - check_matrix(); - result.multiply(_matrix, previous); + result.multiply(_joint->_skinning_matrix, previous); } /** @@ -80,9 +76,7 @@ mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const { */ void JointVertexTransform:: accumulate_matrix(LMatrix4 &accum, PN_stdfloat weight) const { - check_matrix(); - - accum.accumulate(_matrix, weight); + accum.accumulate(_joint->_skinning_matrix, weight); } /** @@ -93,19 +87,6 @@ output(ostream &out) const { out << _joint->get_name(); } -/** - * Recomputes _matrix if it needs it. Uses locking. - */ -void JointVertexTransform:: -compute_matrix() { - LightMutexHolder holder(_lock); - if (_matrix_stale) { - _matrix = _joint->_initial_net_transform_inverse * _joint->_net_transform; - _matrix_stale = false; - } -} - - /** * Tells the BamReader how to create objects of type JointVertexTransform. */ @@ -165,6 +146,5 @@ fillin(DatagramIterator &scan, BamReader *manager) { VertexTransform::fillin(scan, manager); manager->read_pointer(scan); - _matrix_stale = true; mark_modified(Thread::get_current_thread()); } diff --git a/panda/src/char/jointVertexTransform.h b/panda/src/char/jointVertexTransform.h index 39b531de8d..1be097eba6 100644 --- a/panda/src/char/jointVertexTransform.h +++ b/panda/src/char/jointVertexTransform.h @@ -47,15 +47,8 @@ PUBLISHED: virtual void output(ostream &out) const; private: - INLINE void check_matrix() const; - void compute_matrix(); - PT(CharacterJoint) _joint; - LMatrix4 _matrix; - bool _matrix_stale; - LightMutex _lock; - public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); From 2563b652498eb37ca1d7ea69f52634699a8c35ed Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Mar 2018 16:32:33 +0100 Subject: [PATCH 28/32] video4linux: support greyscale pixel format (eg. IR cameras) --- panda/src/vision/webcamVideoCursorV4L.cxx | 5 +++++ panda/src/vision/webcamVideoV4L.cxx | 1 + 2 files changed, 6 insertions(+) diff --git a/panda/src/vision/webcamVideoCursorV4L.cxx b/panda/src/vision/webcamVideoCursorV4L.cxx index 64090c53aa..497115c0dd 100644 --- a/panda/src/vision/webcamVideoCursorV4L.cxx +++ b/panda/src/vision/webcamVideoCursorV4L.cxx @@ -247,6 +247,10 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) { _num_components = 4; break; + case V4L2_PIX_FMT_GREY: + _num_components = 1; + break; + default: vision_cat.error() << "Unsupported pixel format " << src->get_pixel_format() << "!\n"; _ready = false; @@ -484,6 +488,7 @@ fetch_buffer() { case V4L2_PIX_FMT_BGR24: case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_GREY: // Simplest case: copying every row verbatim. nassertr(old_bpl == new_bpl, NULL); diff --git a/panda/src/vision/webcamVideoV4L.cxx b/panda/src/vision/webcamVideoV4L.cxx index b07e1b2071..7d5636f4e0 100644 --- a/panda/src/vision/webcamVideoV4L.cxx +++ b/panda/src/vision/webcamVideoV4L.cxx @@ -174,6 +174,7 @@ void find_all_webcams_v4l() { case V4L2_PIX_FMT_BGR32: case V4L2_PIX_FMT_RGB24: case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_GREY: break; default: From e0f8d7885abcc57222db6ca717e7061a22d5750f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Mar 2018 16:33:26 +0100 Subject: [PATCH 29/32] video4linux: don't block on reading camera frames Add v4l-blocking variable to enable the old behaviour. --- panda/src/vision/config_vision.cxx | 4 ++++ panda/src/vision/config_vision.h | 3 +++ panda/src/vision/webcamVideoCursorV4L.cxx | 12 +++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/panda/src/vision/config_vision.cxx b/panda/src/vision/config_vision.cxx index 3b53c0ed02..50d4b8aeb3 100644 --- a/panda/src/vision/config_vision.cxx +++ b/panda/src/vision/config_vision.cxx @@ -26,6 +26,10 @@ Configure(config_vision); NotifyCategoryDef(vision, ""); +ConfigVariableBool v4l_blocking +("v4l-blocking", false, + PRC_DESC("Set this to true if you want to block waiting for webcam frames.")); + ConfigureFn(config_vision) { init_libvision(); } diff --git a/panda/src/vision/config_vision.h b/panda/src/vision/config_vision.h index 5eec1a95b2..1ad2aaa59d 100644 --- a/panda/src/vision/config_vision.h +++ b/panda/src/vision/config_vision.h @@ -16,9 +16,12 @@ #include "pandabase.h" #include "notifyCategoryProxy.h" +#include "configVariableBool.h" NotifyCategoryDecl(vision, EXPCL_VISION, EXPTP_VISION); +extern ConfigVariableBool v4l_blocking; + extern EXPCL_VISION void init_libvision(); #endif diff --git a/panda/src/vision/webcamVideoCursorV4L.cxx b/panda/src/vision/webcamVideoCursorV4L.cxx index 497115c0dd..a78c55135a 100644 --- a/panda/src/vision/webcamVideoCursorV4L.cxx +++ b/panda/src/vision/webcamVideoCursorV4L.cxx @@ -209,7 +209,13 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) { _buffers = NULL; _buflens = NULL; - _fd = open(src->_device.c_str(), O_RDWR); + + int mode = O_RDWR; + if (!v4l_blocking) { + mode = O_NONBLOCK; + } + + _fd = open(src->_device.c_str(), mode); if (-1 == _fd) { vision_cat.error() << "Failed to open " << src->_device.c_str() << "\n"; return; @@ -397,6 +403,10 @@ fetch_buffer() { vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) { + if (errno == EAGAIN) { + // Simply nothing is available yet. + return NULL; + } vision_cat.error() << "Failed to dequeue buffer!\n"; return NULL; } From 319b3315534637dcd2f7d0d82439279e253323e9 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Mar 2018 16:40:21 +0100 Subject: [PATCH 30/32] ShaderGenerator: fix M_modulate_gloss regression It was mapping the alpha channel of M_modulate_gloss to the glow channel. --- panda/src/pgraphnodes/shaderGenerator.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index f741b02db8..372bd89e1d 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -344,7 +344,7 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { case TextureStage::M_modulate_gloss: if (shader_attrib->auto_gloss_on()) { - info._flags = ShaderKey::TF_map_glow; + info._flags = ShaderKey::TF_map_gloss; } else { info._mode = TextureStage::M_modulate; info._flags = ShaderKey::TF_has_rgb; From 8e8283cbe1f2845ae7ade0244b8649437638ef82 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Mar 2018 22:36:17 +0100 Subject: [PATCH 31/32] android: enable writing stdout/stderr to a socket This can be done by setting the extra string org.panda3d.OUTPUT_URI to tcp://host:port Writing to a log file can still be done using file:///path/to/log.txt [skip ci] --- panda/src/android/PandaActivity.java | 4 +- panda/src/android/android_main.cxx | 77 ++++++++++++++++++++-------- panda/src/android/pview_manifest.xml | 1 + 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/panda/src/android/PandaActivity.java b/panda/src/android/PandaActivity.java index a4413a2816..36a8c97521 100644 --- a/panda/src/android/PandaActivity.java +++ b/panda/src/android/PandaActivity.java @@ -87,9 +87,9 @@ public class PandaActivity extends NativeActivity { return path; } - public String getIntentOutputPath() { + public String getIntentOutputUri() { Intent intent = getIntent(); - return intent.getStringExtra("org.panda3d.OUTPUT_PATH"); + return intent.getStringExtra("org.panda3d.OUTPUT_URI"); } public String getCacheDirString() { diff --git a/panda/src/android/android_main.cxx b/panda/src/android/android_main.cxx index 7d4e75e4b2..0565ef5835 100644 --- a/panda/src/android/android_main.cxx +++ b/panda/src/android/android_main.cxx @@ -17,11 +17,14 @@ #include "virtualFileSystem.h" #include "filename.h" #include "thread.h" +#include "urlSpec.h" #include "config_display.h" // #define OPENGLES_1 #include "config_androiddisplay.h" #include +#include +#include // struct android_app* panda_android_app = NULL; @@ -67,6 +70,55 @@ void android_main(struct android_app* app) { android_cat.info() << "New native activity started on " << *current_thread << "\n"; + // Were we given an optional location to write the stdout/stderr streams? + methodID = env->GetMethodID(activity_class, "getIntentOutputUri", "()Ljava/lang/String;"); + jstring joutput_uri = (jstring) env->CallObjectMethod(activity->clazz, methodID); + if (joutput_uri != nullptr) { + const char *output_uri = env->GetStringUTFChars(joutput_uri, nullptr); + + if (output_uri != nullptr && output_uri[0] != 0) { + URLSpec spec(output_uri); + + if (spec.get_scheme() == "file") { + string path = spec.get_path(); + int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY); + if (fd != -1) { + android_cat.info() + << "Writing standard output to file " << path << "\n"; + + dup2(fd, 1); + dup2(fd, 2); + } else { + android_cat.error() + << "Failed to open output path " << path << "\n"; + } + } else if (spec.get_scheme() == "tcp") { + string host = spec.get_server(); + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in serv_addr = {0}; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(spec.get_port()); + serv_addr.sin_addr.s_addr = inet_addr(host.c_str()); + if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + android_cat.info() + << "Writing standard output to socket " + << spec.get_server_and_port() << "\n"; + dup2(fd, 1); + dup2(fd, 2); + } else { + android_cat.error() + << "Failed to open output socket " + << spec.get_server_and_port() << "\n"; + } + close(fd); + } else { + android_cat.error() + << "Unsupported scheme in output URI: " << output_uri << "\n"; + } + env->ReleaseStringUTFChars(joutput_uri, output_uri); + } + } + // Fetch the data directory. jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); @@ -186,28 +238,6 @@ void android_main(struct android_app* app) { } } - // Were we given an optional location to write the stdout/stderr streams? - methodID = env->GetMethodID(activity_class, "getIntentOutputPath", "()Ljava/lang/String;"); - jstring joutput_path = (jstring) env->CallObjectMethod(activity->clazz, methodID); - if (joutput_path != nullptr) { - const char *output_path = env->GetStringUTFChars(joutput_path, nullptr); - - if (output_path != nullptr && output_path[0] != 0) { - int fd = open(output_path, O_CREAT | O_TRUNC | O_WRONLY); - if (fd != -1) { - android_cat.info() - << "Writing standard output to file " << output_path << "\n"; - - dup2(fd, 1); - dup2(fd, 2); - } else { - android_cat.error() - << "Failed to open output path " << output_path << "\n"; - } - env->ReleaseStringUTFChars(joutput_path, output_path); - } - } - // Create bogus argc and argv for calling the main function. const char *argv[] = {"pview", nullptr, nullptr}; int argc = 1; @@ -266,6 +296,9 @@ void android_main(struct android_app* app) { env->ReleaseStringUTFChars(filename, filename_str); } + close(1); + close(2); + // Detach the thread before exiting. activity->vm->DetachCurrentThread(); } diff --git a/panda/src/android/pview_manifest.xml b/panda/src/android/pview_manifest.xml index 30125c673f..1560bdd828 100644 --- a/panda/src/android/pview_manifest.xml +++ b/panda/src/android/pview_manifest.xml @@ -7,6 +7,7 @@ + From 94ceace5afefed9bd52ff0f229fd1ca8eb7ecd8b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Mar 2018 22:50:11 +0100 Subject: [PATCH 32/32] android: add activity for running Python programs It can be launched from the termux shell using the provided run_python.sh script, which can communicate with the Panda activity using a socket (which is the only way we can reliably get command-line output back to the program.) The Python script needs to be readable by the Panda activity (which implies it needs to be in /sdcard). The standard library is packed into the .apk, and loaded using zipimport. Extension modules are included using a special naming convention and import hook in order to comply with Android's strict demands on how libraries must be named to be included in an .apk. [skip ci] --- makepanda/makepanda.py | 80 +++++++++++++++++++++++++-- panda/src/android/PythonActivity.java | 23 ++++++++ panda/src/android/pview_manifest.xml | 39 ++++++++++--- panda/src/android/python_main.cxx | 80 +++++++++++++++++++++++++++ panda/src/android/run_pview.sh | 14 +++++ panda/src/android/run_python.sh | 14 +++++ panda/src/android/site.py | 34 ++++++++++++ 7 files changed, 272 insertions(+), 12 deletions(-) create mode 100644 panda/src/android/PythonActivity.java create mode 100644 panda/src/android/python_main.cxx create mode 100755 panda/src/android/run_pview.sh create mode 100755 panda/src/android/run_python.sh create mode 100644 panda/src/android/site.py diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 7a8926efd6..1c8c95724d 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -5082,7 +5082,7 @@ if (PkgSkip("SPEEDTREE")==0): # DIRECTORY: panda/src/testbed/ # -if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0 and GetTarget() != 'android'): +if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0): OPTS=['DIR:panda/src/testbed'] TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx') TargetAdd('pview.exe', input='pview_pview.obj') @@ -5101,6 +5101,7 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('org/panda3d/android/NativeIStream.class', opts=OPTS, input='NativeIStream.java') TargetAdd('org/panda3d/android/NativeOStream.class', opts=OPTS, input='NativeOStream.java') TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java') + TargetAdd('org/panda3d/android/PythonActivity.class', opts=OPTS, input='PythonActivity.java') TargetAdd('p3android_composite1.obj', opts=OPTS, input='p3android_composite1.cxx') TargetAdd('libp3android.dll', input='p3android_composite1.obj') @@ -5111,10 +5112,10 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('android_main.obj', opts=OPTS, input='android_main.cxx') if (not RTDIST and PkgSkip("PVIEW")==0): - TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx') + TargetAdd('libpview_pview.obj', opts=OPTS, input='pview.cxx') TargetAdd('libpview.dll', input='android_native_app_glue.obj') TargetAdd('libpview.dll', input='android_main.obj') - TargetAdd('libpview.dll', input='pview_pview.obj') + TargetAdd('libpview.dll', input='libpview_pview.obj') TargetAdd('libpview.dll', input='libp3framework.dll') if not PkgSkip("EGG"): TargetAdd('libpview.dll', input='libpandaegg.dll') @@ -5122,6 +5123,17 @@ if (not RUNTIME and GetTarget() == 'android'): TargetAdd('libpview.dll', input=COMMON_PANDA_LIBS) TargetAdd('libpview.dll', opts=['MODULE', 'ANDROID']) + if (not RTDIST and PkgSkip("PYTHON")==0): + OPTS += ['PYTHON'] + TargetAdd('ppython_ppython.obj', opts=OPTS, input='python_main.cxx') + TargetAdd('libppython.dll', input='android_native_app_glue.obj') + TargetAdd('libppython.dll', input='android_main.obj') + TargetAdd('libppython.dll', input='ppython_ppython.obj') + TargetAdd('libppython.dll', input='libp3framework.dll') + TargetAdd('libppython.dll', input='libp3android.dll') + TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS) + TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID', 'PYTHON']) + # # DIRECTORY: panda/src/androiddisplay/ # @@ -7505,7 +7517,7 @@ def MakeInstallerAndroid(): continue if '.so.' in line: dep = line.rpartition('.so.')[0] + '.so' - oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target)) + oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) else: dep = line @@ -7516,6 +7528,7 @@ def MakeInstallerAndroid(): copy_library(os.path.realpath(fulldep), dep) break + # Now copy every lib in the lib dir, and its dependencies. for base in os.listdir(source_dir): if not base.startswith('lib'): continue @@ -7527,6 +7540,59 @@ def MakeInstallerAndroid(): continue copy_library(source, base) + # Same for Python extension modules. However, Android is strict about + # library naming, so we have a special naming scheme for these, in + # conjunction with a custom import hook to find these modules. + if not PkgSkip("PYTHON"): + suffix = GetExtensionSuffix() + source_dir = os.path.join(GetOutputDir(), "panda3d") + for base in os.listdir(source_dir): + if not base.endswith(suffix): + continue + modname = base[:-len(suffix)] + source = os.path.join(source_dir, base) + copy_library(source, "libpy.panda3d.{}.so".format(modname)) + + # Same for standard Python modules. + import _ctypes + source_dir = os.path.dirname(_ctypes.__file__) + for base in os.listdir(source_dir): + if not base.endswith('.so'): + continue + modname = base.partition('.')[0] + source = os.path.join(source_dir, base) + copy_library(source, "libpy.{}.so".format(modname)) + + def copy_python_tree(source_root, target_root): + for source_dir, dirs, files in os.walk(source_root): + if 'site-packages' in dirs: + dirs.remove('site-packages') + + if not any(base.endswith('.py') for base in files): + continue + + target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root)) + target_dir = os.path.normpath(target_dir) + os.makedirs(target_dir, 0o755) + + for base in files: + if base.endswith('.py'): + target = os.path.join(target_dir, base) + shutil.copy(os.path.join(source_dir, base), target) + + # Copy the Python standard library to the .apk as well. + from distutils.sysconfig import get_python_lib + stdlib_source = get_python_lib(False, True) + stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info)) + copy_python_tree(stdlib_source, stdlib_target) + + # But also copy over our custom site.py. + shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py")) + + # And now make a site-packages directory containing our direct/panda3d/pandac modules. + for tree in "panda3d", "direct", "pandac": + copy_python_tree(os.path.join(GetOutputDir(), tree), os.path.join(stdlib_target, "site-packages", tree)) + # Copy the models and config files to the virtual assets filesystem. oscmd("mkdir apkroot/assets") oscmd("cp -R %s apkroot/assets/models" % (os.path.join(GetOutputDir(), "models"))) @@ -7545,7 +7611,11 @@ def MakeInstallerAndroid(): oscmd(aapt_cmd) # And add all the libraries to it. - oscmd("cd apkroot && aapt add ../%s classes.dex lib/%s/lib*.so" % (apk_unaligned, SDK["ANDROID_ABI"])) + oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned)) + for path, dirs, files in os.walk('apkroot/lib'): + if files: + rel = os.path.relpath(path, 'apkroot') + oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel)) # Now align the .apk, which is necessary for Android to load it. oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned)) diff --git a/panda/src/android/PythonActivity.java b/panda/src/android/PythonActivity.java new file mode 100644 index 0000000000..0d282d84a5 --- /dev/null +++ b/panda/src/android/PythonActivity.java @@ -0,0 +1,23 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file PythonActivity.java + * @author rdb + * @date 2018-02-04 + */ + +package org.panda3d.android; + +import org.panda3d.android.PandaActivity; + +/** + * This is only declared as a separate class from PandaActivity so that we + * can have two separate activity definitions in ApplicationManifest.xml. + */ +public class PythonActivity extends PandaActivity { +} diff --git a/panda/src/android/pview_manifest.xml b/panda/src/android/pview_manifest.xml index 1560bdd828..b462e4018a 100644 --- a/panda/src/android/pview_manifest.xml +++ b/panda/src/android/pview_manifest.xml @@ -45,13 +45,38 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panda/src/android/python_main.cxx b/panda/src/android/python_main.cxx new file mode 100644 index 0000000000..c1fc0a39fe --- /dev/null +++ b/panda/src/android/python_main.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file python_main.cxx + * @author rdb + * @date 2018-02-12 + */ + +#include "dtoolbase.h" +#include "config_android.h" +#include "executionEnvironment.h" + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#include +#if PY_MAJOR_VERSION >= 3 +#include +#endif + +#include + +/** + * The main entry point for the Python activity. Called by android_main. + */ +int main(int argc, char *argv[]) { + if (argc <= 1) { + return 1; + } + + // Help out Python by telling it which encoding to use + Py_FileSystemDefaultEncoding = "utf-8"; + + Py_SetProgramName(Py_DecodeLocale("ppython", nullptr)); + + // Set PYTHONHOME to the location of the .apk file. + string apk_path = ExecutionEnvironment::get_binary_name(); + Py_SetPythonHome(Py_DecodeLocale(apk_path.c_str(), nullptr)); + + // We need to make zlib available to zipimport, but I don't know how + // we could inject our import hook before Py_Initialize, so instead + // load it as though it were a built-in module. + void *zlib = dlopen("libpy.zlib.so", RTLD_NOW); + if (zlib != nullptr) { + void *init = dlsym(zlib, "PyInit_zlib"); + if (init != nullptr) { + PyImport_AppendInittab("zlib", (PyObject *(*)())init); + } + } + + Py_Initialize(); + + // This is used by the import hook to locate the module libraries. + Filename dtool_name = ExecutionEnvironment::get_dtool_name(); + string native_dir = dtool_name.get_dirname(); + PyObject *py_native_dir = PyUnicode_FromStringAndSize(native_dir.c_str(), native_dir.size()); + PySys_SetObject("_native_library_dir", py_native_dir); + Py_DECREF(py_native_dir); + + int sts = 1; + FILE *fp = fopen(argv[1], "r"); + if (fp != nullptr) { + int res = PyRun_AnyFile(fp, argv[1]); + if (res > 0) { + sts = 0; + } else { + android_cat.error() << "Error running " << argv[1] << "\n"; + PyErr_Print(); + } + } else { + android_cat.error() << "Unable to open " << argv[1] << "\n"; + } + + Py_Finalize(); + return sts; +} diff --git a/panda/src/android/run_pview.sh b/panda/src/android/run_pview.sh new file mode 100755 index 0000000000..6f3b6f1476 --- /dev/null +++ b/panda/src/android/run_pview.sh @@ -0,0 +1,14 @@ +# This script can be used for launching the Panda viewer from the Android +# terminal environment, for example from within termux. It uses a socket +# to pipe the command-line output back to the terminal. + +port=12345 + +if [[ $# -eq 0 ]] ; then + echo "Pass full path of model" + exit 1 +fi + +am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PandaActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1) + +nc -l -p $port diff --git a/panda/src/android/run_python.sh b/panda/src/android/run_python.sh new file mode 100755 index 0000000000..9a8adc710b --- /dev/null +++ b/panda/src/android/run_python.sh @@ -0,0 +1,14 @@ +# This script can be used for launching a Python script from the Android +# terminal environment, for example from within termux. It uses a socket +# to pipe the command-line output back to the terminal. + +port=12345 + +if [[ $# -eq 0 ]] ; then + echo "Pass full path of script" + exit 1 +fi + +am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PythonActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1) + +nc -l -p $port diff --git a/panda/src/android/site.py b/panda/src/android/site.py new file mode 100644 index 0000000000..fd3909ede8 --- /dev/null +++ b/panda/src/android/site.py @@ -0,0 +1,34 @@ +import sys +import os + +from importlib.abc import Loader, MetaPathFinder +from importlib.machinery import ModuleSpec + +if sys.version_info >= (3, 5): + from importlib import _bootstrap_external +else: + from importlib import _bootstrap as _bootstrap_external + +sys.platform = "android" + +class AndroidExtensionFinder(MetaPathFinder): + @classmethod + def find_spec(cls, fullname, path=None, target=None): + soname = 'libpy.' + fullname + '.so' + path = os.path.join(sys._native_library_dir, soname) + + if os.path.exists(path): + loader = _bootstrap_external.ExtensionFileLoader(fullname, path) + return ModuleSpec(fullname, loader, origin=path) + + +def main(): + """Adds the site-packages directory to the sys.path. + Also, registers the import hook for extension modules.""" + + sys.path.append('{0}/lib/python{1}.{2}/site-packages'.format(sys.prefix, *sys.version_info)) + sys.meta_path.append(AndroidExtensionFinder) + + +if not sys.flags.no_site: + main()