diff --git a/.gitignore b/.gitignore index 0b40b42f79..de3e59aef4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,12 @@ /targetroot/ /dstroot/ -# Core dumps +# Core dumps and traces core core.* vgcore.* *.core +*.trace # Editor files/directories *.save @@ -26,6 +27,7 @@ vgcore.* /+DESC /+MANIFEST /pkg-plist +/debug.ks # Produced installer/executables /*.exe @@ -36,6 +38,7 @@ vgcore.* /*.dmg /*.whl /*.txz +/*.apk # CMake /build/ diff --git a/BACKERS.md b/BACKERS.md index b86afc0e71..8c6cd2e965 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D. I ![Benefactors](https://opencollective.com/panda3d/tiers/benefactor.svg?avatarHeight=48&width=600) * Sam Edwards +* Max Voss ## Backers diff --git a/README.md b/README.md index 7d0c5bff54..0213c01610 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Installing Panda3D ================== The latest Panda3D SDK can be downloaded from -[this page](https://www.panda3d.org/download/sdk-1-10-2/). +[this page](https://www.panda3d.org/download/sdk-1-10-3/). If you are familiar with installing Python packages, you can use the following comand: @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win64.zip -https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win32.zip +https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win64.zip +https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win32.zip After acquiring these dependencies, you may simply build Panda3D from the command prompt using the following command. (Change `14.1` to `14` if you are @@ -135,7 +135,7 @@ macOS ----- On macOS, you will need to download a set of precompiled thirdparty packages in order to -compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-mac.tar.gz). +compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-mac.tar.gz). After placing the thirdparty directory inside the panda3d source directory, you may build Panda3D using a command like the following: diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 948891d19d..e5d9ac4f72 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -13,6 +13,7 @@ import stat import struct import imp import string +import time import setuptools import distutils.log @@ -30,6 +31,15 @@ if sys.version_info < (3, 0): # Python 3 defines these subtypes of IOError, but Python 2 doesn't. FileNotFoundError = IOError + # Warn the user. They might be using Python 2 by accident. + print("=================================================================") + print("WARNING: You are using Python 2, which will soon be discontinued.") + print("WARNING: Please use Python 3 for best results and continued") + print("WARNING: support after the EOL date of December 31st, 2019.") + print("=================================================================") + sys.stdout.flush() + time.sleep(4.0) + def _parse_list(input): if isinstance(input, basestring): @@ -229,7 +239,9 @@ class build_apps(setuptools.Command): self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt') self.use_optimized_wheels = True self.optimized_wheel_index = '' - self.pypi_extra_indexes = [] + self.pypi_extra_indexes = [ + 'https://archive.panda3d.org/thirdparty', + ] self.file_handlers = {} self.exclude_dependencies = [ # Windows diff --git a/direct/src/filter/FilterManager.py b/direct/src/filter/FilterManager.py index 4150cba568..c20cbed139 100644 --- a/direct/src/filter/FilterManager.py +++ b/direct/src/filter/FilterManager.py @@ -124,7 +124,7 @@ class FilterManager(DirectObject): return winx,winy - def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None): + def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None): """ Causes the scene to be rendered into the supplied textures instead of into the original window. Puts a fullscreen quad @@ -185,7 +185,10 @@ class FilterManager(DirectObject): # Choose the size of the offscreen buffer. (winx, winy) = self.getScaledSize(1,1,1) - buffer = self.createBuffer("filter-base", winx, winy, texgroup) + if fbprops is not None: + buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops) + else: + buffer = self.createBuffer("filter-base", winx, winy, texgroup) if (buffer == None): return None @@ -236,7 +239,7 @@ class FilterManager(DirectObject): return quad - def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None): + def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None): """ Creates an offscreen buffer for an intermediate computation. Installs a quad into the buffer. Returns @@ -250,7 +253,10 @@ class FilterManager(DirectObject): depthbits = bool(depthtex != None) - buffer = self.createBuffer(name, winx, winy, texgroup, depthbits) + if fbprops is not None: + buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops) + else: + buffer = self.createBuffer(name, winx, winy, texgroup, depthbits) if (buffer == None): return None @@ -287,7 +293,7 @@ class FilterManager(DirectObject): return quad - def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1): + def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None): """ Low-level buffer creation. Not intended for public use. """ winprops = WindowProperties() @@ -297,6 +303,9 @@ class FilterManager(DirectObject): props.setRgbColor(1) props.setDepthBits(depthbits) props.setStereo(self.win.isStereo()) + if fbprops is not None: + props.addProperties(fbprops) + depthtex, colortex, auxtex0, auxtex1 = texgroup if (auxtex0 != None): props.setAuxRgba(1) diff --git a/direct/src/gui/DirectOptionMenu.py b/direct/src/gui/DirectOptionMenu.py index 40d48f7672..74d33b5e9e 100644 --- a/direct/src/gui/DirectOptionMenu.py +++ b/direct/src/gui/DirectOptionMenu.py @@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton): # List of items to display on the popup menu ('items', [], self.setItems), # Initial item to display on menu button - # Can be an interger index or the same string as the button + # Can be an integer index or the same string as the button ('initialitem', None, DGG.INITOPT), # Amount of padding to place around popup button indicator ('popupMarkerBorder', (.1, .1), None), + # The initial position of the popup marker + ('popupMarker_pos', (0, 0, 0), None), # Background color to use to highlight popup menu items ('highlightColor', (.5, .5, .5, 1), None), # Extra scale to use on highlight popup menu items @@ -42,6 +44,8 @@ class DirectOptionMenu(DirectButton): DirectButton.__init__(self, parent) # Record any user specified frame size self.initFrameSize = self['frameSize'] + # Record any user specified popup marker position + self.initPopupMarkerPos = self['popupMarker_pos'] # Create a small rectangular marker to distinguish this button # as a popup menu button self.popupMarker = self.createcomponent( @@ -168,8 +172,13 @@ class DirectOptionMenu(DirectButton): else: # Or base it upon largest item bounds = [self.minX, self.maxX, self.minZ, self.maxZ] - pm.setPos(bounds[1] + pmw/2.0, 0, - bounds[2] + (bounds[3] - bounds[2])/2.0) + if self.initPopupMarkerPos: + # Use specified position + pmPos = list(self.initPopupMarkerPos) + else: + # Or base the position on the frame size. + pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0] + pm.setPos(pmPos[0], pmPos[1], pmPos[2]) # Adjust popup menu button to fit all items (or use user specified # frame size bounds[1] += pmw @@ -184,6 +193,12 @@ class DirectOptionMenu(DirectButton): Adjust popup position if default position puts it outside of visible screen region """ + + # Needed attributes (such as minZ) won't be set unless the user has specified + # items to display. Let's assert that we've given items to work with. + items = self['items'] + assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!' + # Show the menu self.popupMenu.show() # Make sure its at the right scale diff --git a/direct/src/showbase/SfxPlayer.py b/direct/src/showbase/SfxPlayer.py index 4f139a89c9..c50c498236 100644 --- a/direct/src/showbase/SfxPlayer.py +++ b/direct/src/showbase/SfxPlayer.py @@ -53,6 +53,8 @@ class SfxPlayer: d = node.getDistance(listenerNode) else: d = node.getDistance(base.cam) + if not cutoff: + cutoff = self.cutoffDistance if d == None or d > cutoff: volume = 0 else: @@ -70,9 +72,6 @@ class SfxPlayer: self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node=None, listenerNode = None, cutoff = None): if sfx: - if not cutoff: - cutoff = self.cutoffDistance - self.setFinalVolume(sfx, node, volume, listenerNode, cutoff) # don't start over if it's already playing, unless diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 2d32d2b338..23d5388684 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -829,9 +829,11 @@ class ShowBase(DirectObject.DirectObject): win.requestProperties(props) mainWindow = False - if self.win == None: + if self.win is None: mainWindow = True self.win = win + if hasattr(self, 'bufferViewer'): + self.bufferViewer.win = win self.winList.append(win) @@ -1677,13 +1679,19 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) - def attachInputDevice(self, device, prefix=None): + def attachInputDevice(self, device, prefix=None, watch=False): """ This function attaches an input device to the data graph, which will cause the device to be polled and generate events. If a prefix is given and not None, it is used to prefix events generated by this device, separated by a hyphen. + The watch argument can be set to True (as of Panda3D 1.10.3) to set up + the default MouseWatcher to receive inputs from this device, allowing + it to be polled via mouseWatcherNode and control user interfaces. + Setting this to True will also make it generate unprefixed events, + regardless of the specified prefix. + If you call this, you should consider calling detachInputDevice when you are done with the device or when it is disconnected. """ @@ -1694,13 +1702,17 @@ class ShowBase(DirectObject.DirectObject): idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name)) # Setup the button thrower to generate events for the device. - bt = idn.attachNewNode(ButtonThrower(device.name)) - if prefix is not None: - bt.node().setPrefix(prefix + '-') + if prefix is not None or not watch: + bt = idn.attachNewNode(ButtonThrower(device.name)) + if prefix is not None: + bt.node().setPrefix(prefix + '-') + self.deviceButtonThrowers.append(bt) assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix)) self.__inputDeviceNodes[device] = idn - self.deviceButtonThrowers.append(bt) + + if watch: + idn.node().addChild(self.mouseWatcherNode) def detachInputDevice(self, device): """ diff --git a/direct/src/stdpy/threading.py b/direct/src/stdpy/threading.py index 45409f9e18..8903a2fbc0 100644 --- a/direct/src/stdpy/threading.py +++ b/direct/src/stdpy/threading.py @@ -201,17 +201,6 @@ class Lock(core.Mutex): def __init__(self, name = "PythonLock"): core.Mutex.__init__(self, name) - def acquire(self, blocking = True): - if blocking: - core.Mutex.acquire(self) - return True - else: - return core.Mutex.tryAcquire(self) - - __enter__ = acquire - - def __exit__(self, t, v, tb): - self.release() class RLock(core.ReMutex): """ This class provides a wrapper around Panda's ReMutex object. @@ -221,18 +210,6 @@ class RLock(core.ReMutex): def __init__(self, name = "PythonRLock"): core.ReMutex.__init__(self, name) - def acquire(self, blocking = True): - if blocking: - core.ReMutex.acquire(self) - return True - else: - return core.ReMutex.tryAcquire(self) - - __enter__ = acquire - - def __exit__(self, t, v, tb): - self.release() - class Condition(core.ConditionVarFull): """ This class provides a wrapper around Panda's ConditionVarFull diff --git a/direct/src/tkpanels/AnimPanel.py b/direct/src/tkpanels/AnimPanel.py index 8cee51e1a6..a8348c299d 100644 --- a/direct/src/tkpanels/AnimPanel.py +++ b/direct/src/tkpanels/AnimPanel.py @@ -9,13 +9,16 @@ __all__ = ['AnimPanel', 'ActorControl'] # Import Tkinter, Pmw, and the floater code from this directory tree. from direct.tkwidgets.AppShell import * from direct.showbase.TkGlobal import * -import Pmw, sys +import Pmw, sys, os from direct.task import Task +from panda3d.core import Filename, getModelPath if sys.version_info >= (3, 0): from tkinter.simpledialog import askfloat + from tkinter.filedialog import askopenfilename else: from tkSimpleDialog import askfloat + from tkFileDialog import askopenfilename FRAMES = 0 @@ -273,7 +276,7 @@ class AnimPanel(AppShell): title = 'Load Animation', parent = self.component('hull') ) - if (animFilename == ''): + if not animFilename: # no file selected, canceled return @@ -369,8 +372,9 @@ class AnimPanel(AppShell): def destroy(self): # First clean up taskMgr.remove(self.id + '_UpdateTask') - self.destroyCallBack() - self.destroyCallBack = None + if self.destroyCallBack is not None: + self.destroyCallBack() + self.destroyCallBack = None AppShell.destroy(self) class ActorControl(Pmw.MegaWidget): diff --git a/direct/src/tkwidgets/Valuator.py b/direct/src/tkwidgets/Valuator.py index 39e0c8d73f..1704948571 100644 --- a/direct/src/tkwidgets/Valuator.py +++ b/direct/src/tkwidgets/Valuator.py @@ -656,25 +656,35 @@ def rgbPanel(nodePath, callback = None, style = 'mini'): pButton.pack(expand = 1, fill = BOTH) # Update menu - menu = vgp.component('menubar').component('Valuator Group-menu') + menubar = vgp.component('menubar') + menubar.deletemenuitems('Valuator Group', 1, 1) + # Some helper functions # Clear color - menu.insert_command(index = 1, label = 'Clear Color', - command = lambda: nodePath.clearColor()) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Clear Color', command=lambda: nodePath.clearColor()) # Set Clear Transparency - menu.insert_command(index = 2, label = 'Set Transparency', - command = lambda: nodePath.setTransparency(1)) - menu.insert_command( - index = 3, label = 'Clear Transparency', - command = lambda: nodePath.clearTransparency()) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Set Transparency', command=lambda: nodePath.setTransparency(1)) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Clear Transparency', command=lambda: nodePath.clearTransparency()) # System color picker - menu.insert_command(index = 4, label = 'Popup Color Picker', - command = popupColorPicker) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Popup Color Picker', command=popupColorPicker) - menu.insert_command(index = 5, label = 'Print to log', - command = printToLog) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Print to log', command=printToLog) + + menubar.addmenuitem( + 'Valuator Group', 'command', 'Dismiss Valuator Group panel', + label='Dismiss', command=vgp.destroy) def setNodePathColor(color): nodePath.setColor(color[0]/255.0, color[1]/255.0, @@ -724,18 +734,23 @@ def lightRGBPanel(light, style = 'mini'): # Update menu button vgp.component('menubar').component('Valuator Group-button')['text'] = ( 'Light Control Panel') + # Add a print button which will also serve as a color tile pButton = Button(vgp.interior(), text = 'Print to Log', bg = getTkColorString(initColor), command = printToLog) pButton.pack(expand = 1, fill = BOTH) + # Update menu - menu = vgp.component('menubar').component('Valuator Group-menu') + menubar = vgp.component('menubar') # System color picker - menu.insert_command(index = 4, label = 'Popup Color Picker', - command = popupColorPicker) - menu.insert_command(index = 5, label = 'Print to log', - command = printToLog) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Popup Color Picker', command=popupColorPicker) + menubar.addmenuitem( + 'Valuator Group', 'command', + label='Print to log', command=printToLog) + def setLightColor(color): light.setColor(Vec4(color[0]/255.0, color[1]/255.0, color[2]/255.0, color[3]/255.0)) diff --git a/doc/ReleaseNotes b/doc/ReleaseNotes index 41d69d7f99..37156c7dab 100644 --- a/doc/ReleaseNotes +++ b/doc/ReleaseNotes @@ -1,3 +1,40 @@ +------------------------ RELEASE 1.10.3 ----------------------- + +This is another bugfix release that addresses a variety of issues +in 1.10.2 and further improves the stability. + +* Fix crash when unplugging certain devices on macOS +* Fix crash on macOS when using RIME input +* Fix logging issues/crashes in apps deployed with Python 2.7 +* Fix issues when starting in fullscreen on Linux/X11 +* Fix mapping of several gamepads including Trust GXT 24 +* Fix Linux crash when no input devices are present +* Unbreak support for matrix arrays in vertex data in OpenGL +* Allow creating multisample FBO in OpenGL with non-MS host window +* Support playing and looping compressed Ogg and WAV audio files +* Fix generation of CollisionBox for transformed geometry in .egg +* Fix Bullet rigid body transform not updating after reparenting +* Fix sporadic color scales with lighting and custom GLSL shader +* Prevent faulty shaders from shutting down GSG on some drivers +* Allow None as either argument to OdeJoint.attach() +* Fix BufferViewer when main window is not opened right away +* Properly detect extension of pz/gz compressed video/audio files +* Fix for invalid behavior of SparseArray methods to clear bits +* FilterManager now allows overriding framebuffer properties +* Fix detection of core-only OpenGL profile on some drivers +* Add gl-forward-compatible config var for OpenGL context creation +* Add paste-emit-keystrokes variable to disable Ctrl+V on Windows +* Fix in-place |= operator on Panda types (such as SparseArray) +* Fix rare FFmpeg "bad src image pointers" errors after seek +* Fix uses of types.InstanceType in some obscure direct functions +* Fix capsule-into-sphere collision test in degenerate case +* KeyboardButton.ascii_key now also accepts a str character +* Fix errors in various Tkinter DIRECT widgets +* Expose save_egg_file/save_egg_data functions in Python API +* Fix assertion error in BoundingBox.set_min_max +* Fix typo in CollisionTraverser.respect_prev_transform property +* Properly install Python bindings when building FreeBSD installer + ------------------------ RELEASE 1.10.2 ----------------------- This release fixes several more bugs, including a few regressions diff --git a/dtool/src/dtoolutil/executionEnvironment.cxx b/dtool/src/dtoolutil/executionEnvironment.cxx index 4152a8c356..79d4a07e04 100644 --- a/dtool/src/dtoolutil/executionEnvironment.cxx +++ b/dtool/src/dtoolutil/executionEnvironment.cxx @@ -85,6 +85,44 @@ extern char **GLOBAL_ARGV; extern int GLOBAL_ARGC; #endif +// One of the responsibilities of ExecutionEnvironment is to determine the path +// to the binary file that contains itself (this is useful for making other +// components able to read files relative to Panda's installation directory). +// When built statically, this is easy - just use the main executable filename. +// When built shared, ExecutionEnvironment will introspect the memory map of +// the running process to look for dynamic library paths matching this list of +// predetermined filenames (ordered most likely to least likely). + +#ifndef LINK_ALL_STATIC +static const char *const libp3dtool_filenames[] = { +#if defined(LIBP3DTOOL_FILENAMES) + + // The build system is communicating the expected filename(s) for the + // libp3dtool dynamic library - no guesswork needed. + LIBP3DTOOL_FILENAMES + +#elif defined(WIN32_VC) + +#ifdef _DEBUG + "libp3dtool_d.dll", +#else + "libp3dtool.dll", +#endif + +#elif defined(__APPLE__) + + "libp3dtool." PANDA_ABI_VERSION_STR ".dylib", + "libp3dtool.dylib", + +#else + + "libp3dtool.so." PANDA_ABI_VERSION_STR, + "libp3dtool.so", + +#endif +}; +#endif /* !LINK_ALL_STATIC */ + // Linux with GNU libc does have global argvargc variables, but we can't // safely access them at stat init time--at least, not in libc5. (It does seem // to work with glibc2, however.) @@ -546,13 +584,14 @@ read_args() { // First, we need to fill in _dtool_name. This contains the full path to // the p3dtool library. -#ifdef WIN32_VC -#ifdef _DEBUG - HMODULE dllhandle = GetModuleHandle("libp3dtool_d.dll"); -#else - HMODULE dllhandle = GetModuleHandle("libp3dtool.dll"); -#endif - if (dllhandle != 0) { +#ifndef LINK_ALL_STATIC +#if defined(WIN32_VC) + for (const char *filename : libp3dtool_filenames) { + if (!_dtool_name.empty()) break; + + HMODULE dllhandle = GetModuleHandle(filename); + if (!dllhandle) continue; + static const DWORD buffer_size = 1024; wchar_t buffer[buffer_size]; DWORD size = GetModuleFileNameW(dllhandle, buffer, buffer_size); @@ -562,46 +601,44 @@ read_args() { _dtool_name = tmp; } } -#endif -#if defined(__APPLE__) +#elif defined(__APPLE__) // And on OSX we don't have procselfmaps, but some _dyld_* functions. - if (_dtool_name.empty()) { - uint32_t ic = _dyld_image_count(); - for (uint32_t i = 0; i < ic; ++i) { - const char *buffer = _dyld_get_image_name(i); - const char *tail = strrchr(buffer, '/'); - if (tail && (strcmp(tail, "/libp3dtool." PANDA_ABI_VERSION_STR ".dylib") == 0 - || strcmp(tail, "/libp3dtool.dylib") == 0)) { + uint32_t ic = _dyld_image_count(); + for (uint32_t i = 0; i < ic; ++i) { + if (!_dtool_name.empty()) break; + + const char *buffer = _dyld_get_image_name(i); + if (!buffer) continue; + const char *tail = strrchr(buffer, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { _dtool_name = buffer; + break; } } } -#endif -#if defined(RTLD_DI_ORIGIN) +#elif defined(RTLD_DI_ORIGIN) // When building with glibc/uClibc, we typically have access to RTLD_DI_ORIGIN in Unix-like operating systems. char origin[PATH_MAX + 1]; - if (_dtool_name.empty()) { - void *dtool_handle = dlopen("libp3dtool.so." PANDA_ABI_VERSION_STR, RTLD_NOW | RTLD_NOLOAD); + for (const char *filename : libp3dtool_filenames) { + if (!_dtool_name.empty()) break; + + void *dtool_handle = dlopen(filename, RTLD_NOW | RTLD_NOLOAD); if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) { _dtool_name = origin; - _dtool_name += "/libp3dtool.so." PANDA_ABI_VERSION_STR; - } else { - // Try the version of libp3dtool.so without ABI suffix. - dtool_handle = dlopen("libp3dtool.so", RTLD_NOW | RTLD_NOLOAD); - if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) { - _dtool_name = origin; - _dtool_name += "/libp3dtool.so"; - } + _dtool_name += '/'; + _dtool_name += filename; } } -#endif -#if !defined(RTLD_DI_ORIGIN) && defined(RTLD_DI_LINKMAP) +#elif defined(RTLD_DI_LINKMAP) // On platforms without RTLD_DI_ORIGIN, we can use dlinfo with RTLD_DI_LINKMAP to get the origin of a loaded library. if (_dtool_name.empty()) { struct link_map *map; @@ -610,16 +647,24 @@ read_args() { #else void *self = dlopen(NULL, RTLD_NOW | RTLD_NOLOAD); #endif - dlinfo(self, RTLD_DI_LINKMAP, &map); + if (dlinfo(self, RTLD_DI_LINKMAP, &map)) { + while (map != nullptr) { + if (!_dtool_name.empty()) break; - while (map != nullptr) { - const char *tail = strrchr(map->l_name, '/'); - const char *head = strchr(map->l_name, '/'); - if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 - || strcmp(tail, "/libp3dtool.so") == 0)) { - _dtool_name = head; + const char *head = strchr(map->l_name, '/'); + if (!head) continue; + const char *tail = strrchr(head, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { + _dtool_name = head; + break; + } + } + + map = map->l_next; } - map = map->l_next; } } #endif @@ -634,19 +679,27 @@ read_args() { pifstream maps("/proc/self/maps"); #endif while (!maps.fail() && !maps.eof()) { + if (!_dtool_name.empty()) break; + char buffer[PATH_MAX]; buffer[0] = 0; maps.getline(buffer, PATH_MAX); - const char *tail = strrchr(buffer, '/'); const char *head = strchr(buffer, '/'); - if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0 - || strcmp(tail, "/libp3dtool.so") == 0)) { - _dtool_name = head; + if (!head) continue; + const char *tail = strrchr(head, '/'); + if (!tail) continue; + + for (const char *filename : libp3dtool_filenames) { + if (strcmp(&tail[1], filename) == 0) { + _dtool_name = head; + break; + } } } maps.close(); } #endif +#endif /* !LINK_ALL_STATIC */ // Now, we need to fill in _binary_name. This contains the full path to the // currently running executable. diff --git a/dtool/src/dtoolutil/iostream_ext.cxx b/dtool/src/dtoolutil/iostream_ext.cxx new file mode 100644 index 0000000000..47f6140e59 --- /dev/null +++ b/dtool/src/dtoolutil/iostream_ext.cxx @@ -0,0 +1,317 @@ +/** + * 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 iostream_ext.cxx + * @author rdb + * @date 2017-07-24 + */ + +#include "iostream_ext.h" + +#ifdef HAVE_PYTHON + +#ifndef CPPPARSER +extern struct Dtool_PyTypedObject Dtool_std_istream; +#endif + +/** + * Reads the given number of bytes from the stream, returned as bytes object. + * If the given size is -1, all bytes are read from the stream. + */ +PyObject *Extension:: +read(int size) { + if (size < 0) { + return readall(); + } + + char *buffer; + std::streamsize read_bytes = 0; + + if (size > 0) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + buffer = (char *)alloca((size_t)size); + read_bytes = buf->sgetn(buffer, (size_t)size); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + } + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(buffer, read_bytes); +#else + return PyString_FromStringAndSize(buffer, read_bytes); +#endif +} + +/** + * Reads from the underlying stream, but using at most one call. The number + * of returned bytes may therefore be less than what was requested, but it + * will always be greater than 0 until EOF is reached. + */ +PyObject *Extension:: +read1(int size) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::streamsize avail = buf->in_avail(); + if (avail == 0) { + avail = 4096; + } + + if (size >= 0 && (std::streamsize)size < avail) { + avail = (std::streamsize)size; + } + + // Don't read more than 4K at a time + if (avail > 4096) { + avail = 4096; + } + + char *buffer = (char *)alloca(avail); + std::streamsize read_bytes = buf->sgetn(buffer, avail); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(buffer, read_bytes); +#else + return PyString_FromStringAndSize(buffer, read_bytes); +#endif +} + +/** + * Reads all of the bytes in the stream. + */ +PyObject *Extension:: +readall() { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::vector result; + + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + + std::streamsize count = buf->sgetn(buffer, buffer_size); + while (count != 0) { + thread_consider_yield(); + result.insert(result.end(), buffer, buffer + count); + count = buf->sgetn(buffer, buffer_size); + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize((char *)result.data(), result.size()); +#else + return PyString_FromStringAndSize((char *)result.data(), result.size()); +#endif +} + +/** + * Reads bytes into a preallocated, writable, bytes-like object, returning the + * number of bytes read. + */ +std::streamsize Extension:: +readinto(PyObject *b) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, 0); + + Py_buffer view; + if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG) == -1) { + PyErr_SetString(PyExc_TypeError, + "write() requires a contiguous, read-write bytes-like object"); + return 0; + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::streamsize count = buf->sgetn((char *)view.buf, view.len); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + + PyBuffer_Release(&view); + return count; +} + +/** + * Extracts one line up to and including the trailing newline character. + * Returns empty string when the end of file is reached. + */ +PyObject *Extension:: +readline(int size) { + std::streambuf *buf = _this->rdbuf(); + nassertr(buf != nullptr, nullptr); + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS +#endif + + std::string line; + int ch = buf->sbumpc(); + while (ch != EOF && (--size) != 0) { + line.push_back(ch); + if (ch == '\n') { + // Here's the newline character. + break; + } + ch = buf->sbumpc(); + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + Py_BLOCK_THREADS +#endif + +#if PY_MAJOR_VERSION >= 3 + return PyBytes_FromStringAndSize(line.data(), line.size()); +#else + return PyString_FromStringAndSize(line.data(), line.size()); +#endif +} + +/** + * Reads all the lines at once and returns a list. Also see the documentation + * for readline(). + */ +PyObject *Extension:: +readlines(int hint) { + PyObject *lst = PyList_New(0); + if (lst == nullptr) { + return nullptr; + } + + PyObject *py_line = readline(-1); + + if (hint < 0) { + while (Py_SIZE(py_line) > 0) { + PyList_Append(lst, py_line); + Py_DECREF(py_line); + + py_line = readline(-1); + } + } else { + size_t totchars = 0; + while (Py_SIZE(py_line) > 0) { + totchars += Py_SIZE(py_line); + PyList_Append(lst, py_line); + Py_DECREF(py_line); + + if (totchars > hint) { + break; + } + + py_line = readline(-1); + } + } + + return lst; +} + +/** + * Yields continuously to read all the lines from the istream. + */ +static PyObject *gen_next(PyObject *self) { + istream *stream = nullptr; + if (!Dtool_Call_ExtractThisPointer(self, Dtool_std_istream, (void **)&stream)) { + return nullptr; + } + + PyObject *line = invoke_extension(stream).readline(); + if (Py_SIZE(line) > 0) { + return line; + } else { + PyErr_SetObject(PyExc_StopIteration, nullptr); + return nullptr; + } +} + +/** + * Iterates over the lines of the file. + */ +PyObject *Extension:: +__iter__(PyObject *self) { + return Dtool_NewGenerator(self, &gen_next); +} + +/** + * Writes the bytes object to the stream. + */ +void Extension:: +write(PyObject *b) { + std::streambuf *buf = _this->rdbuf(); + nassertv(buf != nullptr); + + Py_buffer view; + if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG_RO) == -1) { + PyErr_SetString(PyExc_TypeError, "write() requires a contiguous buffer"); + return; + } + +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + buf->sputn((const char *)view.buf, view.len); + Py_BLOCK_THREADS +#else + buf->sputn((const char *)view.buf, view.len); +#endif + + PyBuffer_Release(&view); +} + +/** + * Write a list of lines to the stream. Line separators are not added, so it + * is usual for each of the lines provided to have a line separator at the + * end. + */ +void Extension:: +writelines(PyObject *lines) { + PyObject *seq = PySequence_Fast(lines, "writelines() expects a sequence"); + if (seq == nullptr) { + return; + } + + PyObject **items = PySequence_Fast_ITEMS(seq); + Py_ssize_t len = PySequence_Fast_GET_SIZE(seq); + + for (Py_ssize_t i = 0; i < len; ++i) { + write(items[i]); + } + + Py_DECREF(seq); +} + +#endif // HAVE_PYTHON diff --git a/dtool/src/dtoolutil/iostream_ext.h b/dtool/src/dtoolutil/iostream_ext.h new file mode 100644 index 0000000000..e8413afa91 --- /dev/null +++ b/dtool/src/dtoolutil/iostream_ext.h @@ -0,0 +1,53 @@ +/** + * 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 iostream_ext.h + * @author rdb + * @date 2017-07-24 + */ + +#ifndef IOSTREAM_EXT_H +#define IOSTREAM_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include +#include "py_panda.h" + +/** + * These classes define the extension methods for istream and ostream, which + * are called instead of any C++ methods with the same prototype. + * + * These are designed to allow streams to be treated as file-like objects. + */ +template<> +class Extension : public ExtensionBase { +public: + PyObject *read(int size=-1); + PyObject *read1(int size=-1); + PyObject *readall(); + std::streamsize readinto(PyObject *b); + + PyObject *readline(int size=-1); + PyObject *readlines(int hint=-1); + PyObject *__iter__(PyObject *self); +}; + +template<> +class Extension : public ExtensionBase { +public: + void write(PyObject *b); + void writelines(PyObject *lines); +}; + +#endif // HAVE_PYTHON + +#endif // IOSTREAM_EXT_H diff --git a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx index 2cd825ff58..ceb2f01bf1 100644 --- a/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx +++ b/dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx @@ -1,3 +1,4 @@ #include "filename_ext.cxx" #include "globPattern_ext.cxx" +#include "iostream_ext.cxx" #include "textEncoder_ext.cxx" diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index bfcac69506..af30943212 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -431,6 +431,12 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap, return true; } + if (method_name == "operator |=") { + def._answer_location = "nb_inplace_or"; + def._wrapper_type = WT_inplace_binary_operator; + return true; + } + if (method_name == "__ipow__") { def._answer_location = "nb_inplace_power"; def._wrapper_type = WT_inplace_ternary_operator; @@ -4946,13 +4952,14 @@ write_function_instance(ostream &out, FunctionRemap *remap, expected_params += "NoneType"; } else if (TypeManager::is_char(type)) { - indent(out, indent_level) << "char " << param_name << default_expr << ";\n"; + indent(out, indent_level) << "char *" << param_name << "_str;\n"; + indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n"; - format_specifiers += "c"; - parameter_list += ", &" + param_name; + format_specifiers += "s#"; + parameter_list += ", &" + param_name + "_str, &" + param_name + "_len"; + extra_param_check << " && " << param_name << "_len == 1"; - // extra_param_check << " && isascii(" << param_name << ")"; - pexpr_string = "(char) " + param_name; + pexpr_string = param_name + "_str[0]"; expected_params += "char"; only_pyobjects = false; diff --git a/dtool/src/parser-inc/iostream b/dtool/src/parser-inc/iostream index 8bfe91d0aa..c6c2edffab 100644 --- a/dtool/src/parser-inc/iostream +++ b/dtool/src/parser-inc/iostream @@ -1,16 +1,15 @@ -// Filename: iostream -// Created by: drose (12May00) -// -//////////////////////////////////////////////////////////////////// -// -// 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." -// -//////////////////////////////////////////////////////////////////// +/** + * 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 iostream + * @author drose + * @date 2000-05-12 + */ // This file, and all the other files in this directory, aren't // intended to be compiled--they're just parsed by CPPParser (and @@ -34,6 +33,9 @@ namespace std { __published: ostream(const ostream&) = delete; + __extension void write(PyObject *b); + __extension void writelines(PyObject *lines); + void put(char c); void flush(); streampos tellp(); @@ -43,10 +45,20 @@ namespace std { protected: ostream(ostream &&); }; + class istream : virtual public ios { __published: istream(const istream&) = delete; + __extension PyObject *read(int size=-1); + __extension PyObject *read1(int size=-1); + __extension PyObject *readall(); + __extension std::streamsize readinto(PyObject *b); + + __extension PyObject *readline(int size=-1); + __extension PyObject *readlines(int hint=-1); + __extension PyObject *__iter__(PyObject *self); + int get(); streampos tellg(); void seekg(streampos pos); @@ -55,6 +67,7 @@ namespace std { protected: istream(istream &&); }; + class iostream : public istream, public ostream { __published: iostream(const iostream&) = delete; diff --git a/dtool/src/prc/encryptStream.cxx b/dtool/src/prc/encryptStream.cxx index 24656b7416..90e9e33858 100644 --- a/dtool/src/prc/encryptStream.cxx +++ b/dtool/src/prc/encryptStream.cxx @@ -12,3 +12,23 @@ */ #include "encryptStream.h" + +/** + * Must be called immediately after open_read(). Decrypts the given number of + * bytes and checks that they match. The amount of header bytes are added to + * an offset so that skipping to 0 will skip past the header. + * + * Returns true if the read magic matches the given magic, false on error. + */ +bool IDecryptStream:: +read_magic(const char *magic, size_t size) { + char *this_magic = (char *)alloca(size); + read(this_magic, size); + + if (!fail() && gcount() == size && memcmp(this_magic, magic, size) == 0) { + _buf.set_magic_length(size); + return true; + } else { + return false; + } +} diff --git a/dtool/src/prc/encryptStream.h b/dtool/src/prc/encryptStream.h index 94deaeb62e..6605ea5998 100644 --- a/dtool/src/prc/encryptStream.h +++ b/dtool/src/prc/encryptStream.h @@ -53,6 +53,9 @@ PUBLISHED: MAKE_PROPERTY(key_length, get_key_length); MAKE_PROPERTY(iteration_count, get_iteration_count); +public: + bool read_magic(const char *magic, size_t size); + private: EncryptStreamBuf _buf; }; diff --git a/dtool/src/prc/encryptStreamBuf.I b/dtool/src/prc/encryptStreamBuf.I index cbf9f59470..b840625fc1 100644 --- a/dtool/src/prc/encryptStreamBuf.I +++ b/dtool/src/prc/encryptStreamBuf.I @@ -81,3 +81,21 @@ INLINE int EncryptStreamBuf:: get_iteration_count() const { return _iteration_count; } + +/** + * Sets the amount of the encrypted data at the beginning that are skipped + * when seeking back to zero. + */ +INLINE void EncryptStreamBuf:: +set_magic_length(size_t length) { + _magic_length = length; +} + +/** + * Sets the amount of the encrypted data at the beginning that are skipped + * when seeking back to zero. + */ +INLINE size_t EncryptStreamBuf:: +get_magic_length() const { + return _magic_length; +} diff --git a/dtool/src/prc/encryptStreamBuf.cxx b/dtool/src/prc/encryptStreamBuf.cxx index fe562e59e6..b087c21fae 100644 --- a/dtool/src/prc/encryptStreamBuf.cxx +++ b/dtool/src/prc/encryptStreamBuf.cxx @@ -177,6 +177,7 @@ open_read(std::istream *source, bool owns_source, const std::string &password) { _read_overflow_buffer = new unsigned char[_read_block_size]; _in_read_overflow_buffer = 0; + _finished = false; thread_consider_yield(); } @@ -322,6 +323,57 @@ close_write() { } } +/** + * Implements seeking within the stream. EncryptStreamBuf only allows seeking + * back to the beginning of the stream. + */ +std::streampos EncryptStreamBuf:: +seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which) { + if (which != std::ios::in) { + // We can only do this with the input stream. + return -1; + } + + if (off != 0 || dir != std::ios::beg) { + // We only know how to reposition to the beginning. + return -1; + } + + size_t n = egptr() - gptr(); + gbump(n); + + if (_source->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { + int result = EVP_DecryptInit(_read_ctx, nullptr, nullptr, nullptr); + nassertr_always(result > 0, -1); + + _source->clear(); + _in_read_overflow_buffer = 0; + _finished = false; + + // Skip past the header. + int iv_length = EVP_CIPHER_CTX_iv_length(_read_ctx); + _source->ignore(6 + iv_length); + + // Ignore the magic bytes. + size_t magic_length = get_magic_length(); + char *buffer = (char *)alloca(magic_length); + if (read_chars(buffer, magic_length) == magic_length) { + return 0; + } + } + + return -1; +} + +/** + * Implements seeking within the stream. EncryptStreamBuf only allows seeking + * back to the beginning of the stream. + */ +std::streampos EncryptStreamBuf:: +seekpos(std::streampos pos, ios_openmode which) { + return seekoff(pos, std::ios::beg, which); +} + /** * Called by the system ostream implementation when its internal buffer is * filled, plus one character. @@ -423,7 +475,7 @@ read_chars(char *start, size_t length) { do { // Get more bytes from the stream. - if (_read_ctx == nullptr) { + if (_read_ctx == nullptr || _finished) { return 0; } @@ -439,8 +491,7 @@ read_chars(char *start, size_t length) { } else { result = EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read); - EVP_CIPHER_CTX_free(_read_ctx); - _read_ctx = nullptr; + _finished = true; } if (result <= 0) { diff --git a/dtool/src/prc/encryptStreamBuf.h b/dtool/src/prc/encryptStreamBuf.h index 7bc4db5199..2922aec939 100644 --- a/dtool/src/prc/encryptStreamBuf.h +++ b/dtool/src/prc/encryptStreamBuf.h @@ -44,6 +44,12 @@ public: INLINE void set_iteration_count(int iteration_count); INLINE int get_iteration_count() const; + INLINE void set_magic_length(size_t length); + INLINE size_t get_magic_length() const; + + virtual std::streampos seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which); + virtual std::streampos seekpos(std::streampos pos, ios_openmode which); + protected: virtual int overflow(int c); virtual int sync(); @@ -71,6 +77,9 @@ private: EVP_CIPHER_CTX *_write_ctx; size_t _write_block_size; + + size_t _magic_length = 0; + bool _finished = false; }; #include "encryptStreamBuf.I" diff --git a/dtool/src/prc/streamReader.I b/dtool/src/prc/streamReader.I index e15953aeba..36f8b93e92 100644 --- a/dtool/src/prc/streamReader.I +++ b/dtool/src/prc/streamReader.I @@ -43,7 +43,18 @@ StreamReader(const StreamReader ©) : } /** - * The copy constructor does not copy ownership of the stream. + * The move constructor steals ownership of the stream. + */ +INLINE StreamReader:: +StreamReader(StreamReader &&from) noexcept : + _in(from._in), + _owns_stream(from._owns_stream) +{ + from._owns_stream = false; +} + +/** + * The copy assignment operator does not copy ownership of the stream. */ INLINE void StreamReader:: operator = (const StreamReader ©) { @@ -54,6 +65,19 @@ operator = (const StreamReader ©) { _owns_stream = false; } +/** + * The move assignment operator steals ownership of the stream. + */ +INLINE void StreamReader:: +operator = (StreamReader &&from) noexcept { + if (_owns_stream) { + delete _in; + } + _in = from._in; + _owns_stream = from._owns_stream; + from._owns_stream = false; +} + /** * */ diff --git a/dtool/src/prc/streamReader.h b/dtool/src/prc/streamReader.h index a317f0a783..4d32e0616e 100644 --- a/dtool/src/prc/streamReader.h +++ b/dtool/src/prc/streamReader.h @@ -31,7 +31,9 @@ public: PUBLISHED: INLINE explicit StreamReader(std::istream *in, bool owns_stream); INLINE StreamReader(const StreamReader ©); + INLINE StreamReader(StreamReader &&from) noexcept; INLINE void operator = (const StreamReader ©); + INLINE void operator = (StreamReader &&from) noexcept; INLINE ~StreamReader(); INLINE std::istream *get_istream() const; diff --git a/dtool/src/prc/streamWriter.I b/dtool/src/prc/streamWriter.I index b2485d3fc7..1f12c1517e 100644 --- a/dtool/src/prc/streamWriter.I +++ b/dtool/src/prc/streamWriter.I @@ -51,7 +51,21 @@ StreamWriter(const StreamWriter ©) : } /** - * The copy constructor does not copy ownership of the stream. + * The move constructor steals ownership of the stream. + */ +INLINE StreamWriter:: +StreamWriter(StreamWriter &&from) noexcept : +#ifdef HAVE_PYTHON + softspace(0), +#endif + _out(from._out), + _owns_stream(from._owns_stream) +{ + from._owns_stream = false; +} + +/** + * The copy assignment operator does not copy ownership of the stream. */ INLINE void StreamWriter:: operator = (const StreamWriter ©) { @@ -62,6 +76,19 @@ operator = (const StreamWriter ©) { _owns_stream = false; } +/** + * The move assignment operator steals ownership of the stream. + */ +INLINE void StreamWriter:: +operator = (StreamWriter &&from) noexcept { + if (_owns_stream) { + delete _out; + } + _out = from._out; + _owns_stream = from._owns_stream; + from._owns_stream = false; +} + /** * */ diff --git a/dtool/src/prc/streamWriter.h b/dtool/src/prc/streamWriter.h index cc00ef9ce3..8072e8aa40 100644 --- a/dtool/src/prc/streamWriter.h +++ b/dtool/src/prc/streamWriter.h @@ -32,7 +32,9 @@ public: PUBLISHED: INLINE explicit StreamWriter(std::ostream *out, bool owns_stream); INLINE StreamWriter(const StreamWriter ©); + INLINE StreamWriter(StreamWriter &&from) noexcept; INLINE void operator = (const StreamWriter ©); + INLINE void operator = (StreamWriter &&from) noexcept; INLINE ~StreamWriter(); INLINE std::ostream *get_ostream() const; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 4f2abef8a9..9941fa8175 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -1009,12 +1009,26 @@ if (COMPILER=="GCC"): if GetTarget() == 'darwin': LibName("ALWAYS", "-framework AppKit") + LibName("IOKIT", "-framework IOKit") + LibName("QUARTZ", "-framework Quartz") LibName("AGL", "-framework AGL") LibName("CARBON", "-framework Carbon") LibName("COCOA", "-framework Cocoa") # Fix for a bug in OSX Leopard: LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib") + # Temporary exceptions to removal of this flag + if not PkgSkip("ROCKET"): + LibName("ROCKET", "-undefined dynamic_lookup") + if not PkgSkip("FFMPEG"): + LibName("FFMPEG", "-undefined dynamic_lookup") + if not PkgSkip("ASSIMP"): + LibName("ASSIMP", "-undefined dynamic_lookup") + if not PkgSkip("OPENEXR"): + LibName("OPENEXR", "-undefined dynamic_lookup") + if not PkgSkip("VRPN"): + LibName("VRPN", "-undefined dynamic_lookup") + if GetTarget() == 'android': LibName("ALWAYS", '-llog') LibName("ANDROID", '-landroid') @@ -1140,6 +1154,7 @@ def BracketNameWithQuotes(name): # Workaround for OSX bug - compiler doesn't like those flags quoted. if (name.startswith("-framework")): return name if (name.startswith("-dylib_file")): return name + if (name.startswith("-undefined ")): return name # Don't add quotes when it's not necessary. if " " not in name: return name @@ -1813,9 +1828,11 @@ def CompileLink(dll, obj, opts): cmd += ' -Wl,--allow-shlib-undefined' else: if (GetTarget() == "darwin"): - cmd = cxx + ' -undefined dynamic_lookup' - if ("BUNDLE" in opts or GetOrigExt(dll) == ".pyd"): - cmd += ' -bundle ' + cmd = cxx + if GetOrigExt(dll) == ".pyd": + cmd += ' -bundle -undefined dynamic_lookup' + elif "BUNDLE" in opts: + cmd += ' -bundle' else: install_name = '@loader_path/../lib/' + os.path.basename(dll) cmd += ' -dynamiclib -install_name ' + install_name @@ -2325,7 +2342,6 @@ DTOOL_CONFIG=[ ("COMPILE_IN_DEFAULT_FONT", '1', '1'), ("STDFLOAT_DOUBLE", 'UNDEF', 'UNDEF'), ("HAVE_MAYA", '1', '1'), - ("HAVE_SOFTIMAGE", 'UNDEF', 'UNDEF'), ("REPORT_OPENSSL_ERRORS", '1', '1'), ("USE_PANDAFILESTREAM", '1', '1'), ("USE_DELETED_CHAIN", '1', '1'), @@ -2866,6 +2882,14 @@ for basename in del_files: p3d_init = """"Python bindings for the Panda3D libraries" __version__ = '%s' + +if __debug__: + import sys + if sys.version_info < (3, 0): + sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n") + sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n") + sys.stderr.flush() + del sys """ % (WHLVERSION) if GetTarget() == 'windows': @@ -3661,6 +3685,7 @@ IGATEFILES += [ "globPattern_ext.h", "pandaFileStream.h", "lineStream.h", + "iostream_ext.h", ] TargetAdd('libp3dtoolutil.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3dtoolutil.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dtoolutil', 'SRCDIR:dtool/src/dtoolutil']) @@ -4204,7 +4229,7 @@ if (not RUNTIME): OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ', 'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', 'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI', - 'SETUPAPI'] + 'SETUPAPI', 'IOKIT'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') @@ -4843,7 +4868,7 @@ if (GetTarget() == 'darwin' and PkgSkip("COCOA")==0 and PkgSkip("GL")==0 and not if (PkgSkip('PANDAFX')==0): TargetAdd('libpandagl.dll', input='libpandafx.dll') TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS) - TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON']) + TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON', 'QUARTZ']) # # DIRECTORY: panda/src/wgldisplay/ diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index fea29bd17f..e7e143c264 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -596,10 +596,23 @@ def makewheel(version, output_dir, platform=None): # Write the panda3d tree. We use a custom empty __init__ since the # default one adds the bin directory to the PATH, which we don't have. - whl.write_file_data('panda3d/__init__.py', """"Python bindings for the Panda3D libraries" + p3d_init = """"Python bindings for the Panda3D libraries" __version__ = '{0}' -""".format(version)) +""".format(version) + + if '27' in ABI_TAG: + p3d_init += """ +if __debug__: + import sys + if sys.version_info < (3, 0): + sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n") + sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n") + sys.stderr.flush() + del sys +""" + + whl.write_file_data('panda3d/__init__.py', p3d_init) # Copy the extension modules from the panda3d directory. ext_suffix = GetExtensionSuffix() diff --git a/makepanda/test_wheel.py b/makepanda/test_wheel.py index 09b0727fe7..1b6ac14175 100755 --- a/makepanda/test_wheel.py +++ b/makepanda/test_wheel.py @@ -17,31 +17,34 @@ from optparse import OptionParser def test_wheel(wheel, verbose=False): envdir = tempfile.mkdtemp(prefix="venv-") print("Setting up virtual environment in {0}".format(envdir)) - - if sys.version_info >= (3, 0): - subprocess.call([sys.executable, "-m", "venv", "--clear", envdir]) - else: - subprocess.call([sys.executable, "-m", "virtualenv", "--clear", envdir]) + sys.stdout.flush() # Make sure pip is up-to-date first. - if subprocess.call([sys.executable, "-m", "pip", "install", "-U", "pip"]) != 0: - shutil.rmtree(envdir) - sys.exit(1) + subprocess.call([sys.executable, "-B", "-m", "pip", "install", "-U", "pip"]) - # Install pytest into the environment, as well as our wheel. - if sys.platform == "win32": - pip = os.path.join(envdir, "Scripts", "pip.exe") + # Create a virtualenv. + if sys.version_info >= (3, 0): + subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir]) else: - pip = os.path.join(envdir, "bin", "pip") - if subprocess.call([pip, "install", "pytest", wheel]) != 0: - shutil.rmtree(envdir) - sys.exit(1) + subprocess.call([sys.executable, "-B", "-m", "virtualenv", "--clear", envdir]) - # Run the test suite. + # Determine the path to the Python interpreter. if sys.platform == "win32": python = os.path.join(envdir, "Scripts", "python.exe") else: python = os.path.join(envdir, "bin", "python") + + # Upgrade pip inside the environment too. + if subprocess.call([python, "-m", "pip", "install", "-U", "pip"]) != 0: + shutil.rmtree(envdir) + sys.exit(1) + + # Install pytest into the environment, as well as our wheel. + if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0: + shutil.rmtree(envdir) + sys.exit(1) + + # Run the test suite. test_cmd = [python, "-m", "pytest", "tests"] if verbose: test_cmd.append("--verbose") diff --git a/panda/src/audiotraits/openalAudioManager.cxx b/panda/src/audiotraits/openalAudioManager.cxx index edf7dfe222..76e7b0dd81 100644 --- a/panda/src/audiotraits/openalAudioManager.cxx +++ b/panda/src/audiotraits/openalAudioManager.cxx @@ -442,7 +442,7 @@ get_sound_data(MovieAudio *movie, int mode) { int channels = stream->audio_channels(); int samples = (int)(stream->length() * stream->audio_rate()); int16_t *data = new int16_t[samples * channels]; - stream->read_samples(samples, data); + samples = stream->read_samples(samples, data); alBufferData(sd->_sample, (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data, samples * channels * 2, stream->audio_rate()); diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 9244b7509f..5676e63f80 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -373,7 +373,6 @@ read_stream_data(int bytelen, unsigned char *buffer) { nassertr(has_sound_data(), 0); MovieAudioCursor *cursor = _sd->_stream; - double length = cursor->length(); int channels = cursor->audio_channels(); int rate = cursor->audio_rate(); int space = bytelen / (channels * 2); @@ -381,7 +380,7 @@ read_stream_data(int bytelen, unsigned char *buffer) { while (space && (_loops_completed < _playing_loops)) { double t = cursor->tell(); - double remain = length - t; + double remain = cursor->length() - t; if (remain > 60.0) { remain = 60.0; } @@ -403,9 +402,20 @@ read_stream_data(int bytelen, unsigned char *buffer) { if (samples > _sd->_stream->ready()) { samples = _sd->_stream->ready(); } - cursor->read_samples(samples, (int16_t *)buffer); - size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2); - audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval); + samples = cursor->read_samples(samples, (int16_t *)buffer); + if (audio_cat.is_debug()) { + size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2); + audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval); + } + if (samples == 0) { + _loops_completed += 1; + cursor->seek(0.0); + if (_playing_loops >= 1000000000) { + // Prevent infinite loop if endlessly looping empty sound + return fill; + } + continue; + } fill += samples; space -= samples; buffer += (samples * channels * 2); diff --git a/panda/src/bullet/bulletRigidBodyNode.cxx b/panda/src/bullet/bulletRigidBodyNode.cxx index b97f768d1b..acd7e96e79 100644 --- a/panda/src/bullet/bulletRigidBodyNode.cxx +++ b/panda/src/bullet/bulletRigidBodyNode.cxx @@ -359,6 +359,20 @@ do_transform_changed() { } } +/** + * + */ +void BulletRigidBodyNode:: +parents_changed() { + + if (_motion.sync_disabled()) return; + + if (get_num_parents() > 0) { + LightMutexHolder holder(BulletWorld::get_global_lock()); + do_transform_changed(); + } +} + /** * */ diff --git a/panda/src/bullet/bulletRigidBodyNode.h b/panda/src/bullet/bulletRigidBodyNode.h index d0e11da6cf..246dec3e6c 100644 --- a/panda/src/bullet/bulletRigidBodyNode.h +++ b/panda/src/bullet/bulletRigidBodyNode.h @@ -112,6 +112,7 @@ public: void do_sync_b2p(); protected: + virtual void parents_changed(); virtual void transform_changed(); private: diff --git a/panda/src/cocoadisplay/cocoaGraphicsBuffer.h b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h index 3167d2c02f..8485540fdc 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsBuffer.h +++ b/panda/src/cocoadisplay/cocoaGraphicsBuffer.h @@ -21,7 +21,7 @@ * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface * with Cocoa contexts, so that it can be used without a host window. */ -class CocoaGraphicsBuffer : public GLGraphicsBuffer { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsBuffer : public GLGraphicsBuffer { public: CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe, const std::string &name, diff --git a/panda/src/cocoadisplay/cocoaGraphicsPipe.h b/panda/src/cocoadisplay/cocoaGraphicsPipe.h index cc8c8142d5..902d673047 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsPipe.h +++ b/panda/src/cocoadisplay/cocoaGraphicsPipe.h @@ -33,7 +33,7 @@ class FrameBufferProperties; * This graphics pipe represents the interface for creating OpenGL graphics * windows on a Cocoa-based (e.g. Mac OS X) client. */ -class CocoaGraphicsPipe : public GraphicsPipe { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsPipe : public GraphicsPipe { public: CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID()); virtual ~CocoaGraphicsPipe(); diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index c8820a73d3..0463372b8e 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -26,7 +26,7 @@ * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific * information. */ -class CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian { public: INLINE const FrameBufferProperties &get_fb_properties() const; void get_properties(FrameBufferProperties &properties, diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index a053cb11b9..99e8814f63 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -29,7 +29,7 @@ * An interface to the Cocoa system for managing OpenGL windows under Mac OS * X. */ -class CocoaGraphicsWindow : public GraphicsWindow { +class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsWindow : public GraphicsWindow { public: CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, const std::string &name, diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 0a140109c7..42490b43da 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -1618,7 +1618,7 @@ handle_key_event(NSEvent *event) { if ([event type] == NSKeyDown) { // Translate it to a unicode character for keystrokes. I would use // interpretKeyEvents and insertText, but that doesn't handle dead keys. - TISInputSourceRef input_source = TISCopyCurrentKeyboardInputSource(); + TISInputSourceRef input_source = TISCopyCurrentKeyboardLayoutInputSource(); CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData); const UCKeyboardLayout *layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); @@ -1827,7 +1827,7 @@ get_keyboard_map() const { const UCKeyboardLayout *layout; // Get the current keyboard layout data. - input_source = TISCopyCurrentKeyboardInputSource(); + input_source = TISCopyCurrentKeyboardLayoutInputSource(); layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData); layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); diff --git a/panda/src/collide/collisionTraverser.h b/panda/src/collide/collisionTraverser.h index 8a6c912aaf..a51a1bd474 100644 --- a/panda/src/collide/collisionTraverser.h +++ b/panda/src/collide/collisionTraverser.h @@ -49,7 +49,7 @@ PUBLISHED: INLINE void set_respect_prev_transform(bool flag); INLINE bool get_respect_prev_transform() const; - MAKE_PROPERTY(respect_preV_transform, get_respect_prev_transform, + MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform, set_respect_prev_transform); void add_collider(const NodePath &collider, CollisionHandler *handler); diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4ebbc55498..69d2979f7f 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -62,6 +62,15 @@ enum QuirkBits { // We only connect it if it is reporting any events, because when Steam is // running, the Steam controller is muted in favour of a dummy Xbox device. QB_steam_controller = 32, + + // Axes on the right stick are swapped, using x for y and vice versa. + QB_right_axes_swapped = 64, + + // Has no trigger axes. + QB_no_analog_triggers = 128, + + // Alternate button mapping. + QB_alt_button_mapping = 256, }; static const struct DeviceMapping { @@ -81,11 +90,15 @@ static const struct DeviceMapping { // Steam Controller (wireless) {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller}, // Jess Tech Colour Rumble Pad - {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, - // SPEED Link SL-6535-SBK-01 - {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0}, + {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_right_axes_swapped}, + // Trust GXT 24 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping}, // 8bitdo N30 Pro Controller {0x2dc8, 0x9001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z}, + // Generic gamepad + {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping | QB_rstick_from_z | QB_right_axes_swapped}, + // Generic gamepad without sticks + {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping}, // 3Dconnexion Space Traveller 3D Mouse {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Pilot 3D Mouse @@ -488,10 +501,16 @@ init_device() { break; case ABS_Z: if (quirks & QB_rstick_from_z) { - axis = InputDevice::Axis::right_x; + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_y; + } else { + axis = InputDevice::Axis::right_x; + } } else if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::left_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::left_trigger; + have_analog_triggers = true; + } } else if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::z; } else { @@ -514,10 +533,19 @@ init_device() { break; case ABS_RZ: if (quirks & QB_rstick_from_z) { - axis = InputDevice::Axis::right_y; + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_x; + } else { + axis = InputDevice::Axis::right_y; + } } else if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::right_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } else { + // Special weird case for Trust GXT 24 + axis = InputDevice::Axis::right_y; + } } else { axis = InputDevice::Axis::yaw; } @@ -537,8 +565,10 @@ init_device() { break; case ABS_GAS: if (_device_class == DeviceClass::gamepad) { - axis = InputDevice::Axis::right_trigger; - have_analog_triggers = true; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } } else { axis = InputDevice::Axis::accelerator; } @@ -963,6 +993,26 @@ map_button(int code, DeviceClass device_class, int quirks) { // BTN_THUMB and BTN_THUMB2 detect touching the touchpads. return ButtonHandle::none(); + } else if (device_class == DeviceClass::gamepad && + (quirks & QB_alt_button_mapping) != 0) { + static const ButtonHandle mapping[] = { + GamepadButton::face_y(), + GamepadButton::face_b(), + GamepadButton::face_a(), + GamepadButton::face_x(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::ltrigger(), + GamepadButton::rtrigger(), + GamepadButton::back(), + GamepadButton::start(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if ((code & 0xf) < 12) { + return mapping[code & 0xf]; + } + } else if (device_class == DeviceClass::gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index da1369932e..8468fd4816 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -22,8 +22,11 @@ #include "mouseButton.h" static void removal_callback(void *ctx, IOReturn result, void *sender) { - IOKitInputDevice *input_device = (IOKitInputDevice *)ctx; + // We need to hold a reference to this because it may otherwise be destroyed + // during the call to on_remove(). + PT(IOKitInputDevice) input_device = (IOKitInputDevice *)ctx; nassertv(input_device != nullptr); + nassertv(input_device->test_ref_count_integrity()); input_device->on_remove(); } diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 8eed3e1cc3..7b9b9fdf6f 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -61,6 +61,9 @@ LinuxInputDeviceManager() { // We'll want to sort the devices by index, since the order may be // meaningful (eg. for the Xbox wireless receiver). + if (indices.empty()) { + return; + } std::sort(indices.begin(), indices.end()); _evdev_devices.resize(indices.back() + 1, nullptr); diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index e2e27761bc..10262d124c 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -32,6 +32,12 @@ enum QuirkBits : int { // Throttle is reversed. QB_reversed_throttle = 4, + + // Right stick uses Z and Rz inputs. + QB_rstick_from_z = 8, + + // Axes on the right stick are swapped, using x for y and vice versa. + QB_right_axes_swapped = 64, }; // Some nonstandard gamepads have different button mappings. @@ -42,12 +48,17 @@ static const struct DeviceMapping { int quirks; const char *buttons[16]; } mapping_presets[] = { - // SNES-style USB gamepad + // SNES-style USB gamepad, or cheap unbranded USB gamepad with no sticks + // ABXY are mapped based on their position, not based on their label. {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, - {"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", "none", "none", "back", "start"} + {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start"} }, - // SPEED Link SL-6535-SBK-01 - {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, + // Unbranded generic cheap USB gamepad + {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers | QB_right_axes_swapped, + {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"} + }, + // Trust GXT 24 / SPEED Link SL-6535-SBK-01 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers, {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"} }, // T.Flight Hotas X @@ -56,7 +67,7 @@ static const struct DeviceMapping { }, // NVIDIA Shield Controller {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0, - {"face_a", "face_b", "n", "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", "e", "f", "g", "start", "h", "lstick", "rstick", "i"} + {"face_a", "face_b", 0, "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", 0, 0, 0, "start", 0, "lstick", "rstick", 0} }, {0}, }; @@ -422,7 +433,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_Z: if (_device_class == DeviceClass::gamepad) { - if ((quirks & QB_no_analog_triggers) == 0) { + if (quirks & QB_rstick_from_z) { + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_y; + swap(cap.LogicalMin, cap.LogicalMax); + } else { + axis = InputDevice::Axis::right_x; + } + } else if ((quirks & QB_no_analog_triggers) == 0) { axis = Axis::left_trigger; } } else if (_device_class == DeviceClass::flight_stick) { @@ -455,7 +473,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_RZ: if (_device_class == DeviceClass::gamepad) { - if ((quirks & QB_no_analog_triggers) == 0) { + if (quirks & QB_rstick_from_z) { + if (quirks & QB_right_axes_swapped) { + axis = InputDevice::Axis::right_x; + } else { + axis = InputDevice::Axis::right_y; + swap(cap.LogicalMin, cap.LogicalMax); + } + } else if ((quirks & QB_no_analog_triggers) == 0) { axis = Axis::right_trigger; } } else { @@ -481,6 +506,16 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; } + // If this axis already exists, don't double-map it, but take the first + // one. This is important for the Trust GXT 24 / SL-6535-SBK-01 which + // have a weird extra Z axis with DataIndex 2 that should be ignored. + for (size_t i = 0; i < _axes.size(); ++i) { + if (_axes[i].axis == axis) { + axis = Axis::none; + break; + } + } + int axis_index; if (!is_signed) { // All axes on the weird XInput-style mappings go from -1 to 1 diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 749fbc7b35..851ca112ea 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -1901,6 +1901,14 @@ fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) { return (_target_shader->get_shader_input_ptr(spec._arg)); } +/** + * + */ +bool GraphicsStateGuardian:: +fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec, Shader::ShaderPtrData &data) { + return _target_shader->get_shader_input_ptr(spec._arg, data); +} + /** * Makes the specified DisplayRegion current. All future drawing and clear * operations will be constrained within the given DisplayRegion. diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h index e1c9435fef..cb556499b2 100644 --- a/panda/src/display/graphicsStateGuardian.h +++ b/panda/src/display/graphicsStateGuardian.h @@ -339,6 +339,7 @@ public: PT(Texture) fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, int &view); const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec); + bool fetch_ptr_parameter(const Shader::ShaderPtrSpec &spec, Shader::ShaderPtrData &data); virtual void prepare_display_region(DisplayRegionPipelineReader *dr); virtual void clear_before_callback(); diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index 4467132b79..f857940774 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -2068,10 +2068,7 @@ open_read_subfile(Subfile *subfile) { stream = wrapper; // Validate the password by confirming that the encryption header matches. - char this_header[_encrypt_header_size]; - stream->read(this_header, _encrypt_header_size); - if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size || - memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) { + if (!wrapper->read_magic(_encrypt_header, _encrypt_header_size)) { express_cat.error() << "Unable to decrypt subfile " << subfile->_name << ".\n"; delete stream; diff --git a/panda/src/express/zStreamBuf.cxx b/panda/src/express/zStreamBuf.cxx index 877254918d..57a64bed7a 100644 --- a/panda/src/express/zStreamBuf.cxx +++ b/panda/src/express/zStreamBuf.cxx @@ -202,8 +202,8 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) { gbump(n); - _source->seekg(0, ios::beg); - if (_source->tellg() == (streampos)0) { + if (_source->rdbuf()->pubseekpos(0, ios::in) == (streampos)0) { + _source->clear(); _z_source.next_in = Z_NULL; _z_source.avail_in = 0; _z_source.next_out = Z_NULL; diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.cxx b/panda/src/ffmpeg/ffmpegAudioCursor.cxx index 4b17800c2b..2fdc10b983 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.cxx +++ b/panda/src/ffmpeg/ffmpegAudioCursor.cxx @@ -462,7 +462,7 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void FfmpegAudioCursor:: +int FfmpegAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; @@ -486,4 +486,5 @@ read_samples(int n, int16_t *data) { } _samples_read += n; + return n; } diff --git a/panda/src/ffmpeg/ffmpegAudioCursor.h b/panda/src/ffmpeg/ffmpegAudioCursor.h index f3963ff527..77e8890d6c 100644 --- a/panda/src/ffmpeg/ffmpegAudioCursor.h +++ b/panda/src/ffmpeg/ffmpegAudioCursor.h @@ -45,7 +45,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); protected: void fetch_packet(); diff --git a/panda/src/ffmpeg/ffmpegVideoCursor.cxx b/panda/src/ffmpeg/ffmpegVideoCursor.cxx index fa5789b6c4..23314561e9 100644 --- a/panda/src/ffmpeg/ffmpegVideoCursor.cxx +++ b/panda/src/ffmpeg/ffmpegVideoCursor.cxx @@ -755,7 +755,9 @@ do_poll() { PT(FfmpegBuffer) frame = do_alloc_frame(); nassertr(frame != nullptr, false); _lock.release(); - advance_to_frame(seek_frame); + if (seek_frame != _begin_frame) { + advance_to_frame(seek_frame); + } if (_frame_ready) { export_frame(frame); _lock.acquire(); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 6151a2c00d..3ad8e01428 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -597,8 +597,31 @@ reset() { query_glsl_version(); #ifndef OPENGLES - bool core_profile = is_at_least_gl_version(3, 2) && - !has_extension("GL_ARB_compatibility"); + // Determine whether this OpenGL context has compatibility features. + bool core_profile = false; + + if (_gl_version_major >= 3) { + if (_gl_version_major > 3 || _gl_version_minor >= 2) { + // OpenGL 3.2 has a built-in way to check this. + GLint profile_mask = 0; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile_mask); + + if (profile_mask & GL_CONTEXT_CORE_PROFILE_BIT) { + core_profile = true; + } else if (profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) { + core_profile = false; + } else { + core_profile = !has_extension("GL_ARB_compatibility"); + } + } else { + // OpenGL 3.0/3.1. + GLint flags = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &flags); + if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) { + core_profile = true; + } + } + } if (GLCAT.is_debug()) { if (core_profile) { diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index 5a7eda65fc..14264e142a 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -2110,8 +2110,8 @@ issue_parameters(int altered) { for (int i = 0; i < (int)_shader->_ptr_spec.size(); ++i) { Shader::ShaderPtrSpec &spec = _shader->_ptr_spec[i]; - const Shader::ShaderPtrData* ptr_data = _glgsg->fetch_ptr_parameter(spec); - if (ptr_data == nullptr) { //the input is not contained in ShaderPtrData + Shader::ShaderPtrData ptr_data; + if (!_glgsg->fetch_ptr_parameter(spec, ptr_data)) { //the input is not contained in ShaderPtrData release_resources(); return; } @@ -2119,18 +2119,18 @@ issue_parameters(int altered) { nassertd(spec._dim[1] > 0) continue; GLint p = spec._id._seqno; - int array_size = min(spec._dim[0], (int)ptr_data->_size / spec._dim[1]); + int array_size = min(spec._dim[0], (int)ptr_data._size / spec._dim[1]); switch (spec._type) { case Shader::SPT_float: { float *data = nullptr; - switch (ptr_data->_type) { + switch (ptr_data._type) { case Shader::SPT_int: // Convert int data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((int*)ptr_data->_ptr)[i]); + data[i] = (float)(((int*)ptr_data._ptr)[i]); } break; @@ -2138,7 +2138,7 @@ issue_parameters(int altered) { // Convert unsigned int data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((unsigned int*)ptr_data->_ptr)[i]); + data[i] = (float)(((unsigned int*)ptr_data._ptr)[i]); } break; @@ -2146,12 +2146,12 @@ issue_parameters(int altered) { // Downgrade double data to float data. data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]); for (int i = 0; i < (array_size * spec._dim[1]); ++i) { - data[i] = (float)(((double*)ptr_data->_ptr)[i]); + data[i] = (float)(((double*)ptr_data._ptr)[i]); } break; case Shader::SPT_float: - data = (float*)ptr_data->_ptr; + data = (float*)ptr_data._ptr; break; default: @@ -2171,8 +2171,8 @@ issue_parameters(int altered) { break; case Shader::SPT_int: - if (ptr_data->_type != Shader::SPT_int && - ptr_data->_type != Shader::SPT_uint) { + if (ptr_data._type != Shader::SPT_int && + ptr_data._type != Shader::SPT_uint) { GLCAT.error() << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n"; @@ -2183,18 +2183,18 @@ issue_parameters(int altered) { } else { switch (spec._dim[1]) { - case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data->_ptr); continue; - case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data->_ptr); continue; + case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data._ptr); continue; + case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data._ptr); continue; + case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data._ptr); continue; + case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data._ptr); continue; } nassertd(false) continue; } break; case Shader::SPT_uint: - if (ptr_data->_type != Shader::SPT_uint && - ptr_data->_type != Shader::SPT_int) { + if (ptr_data._type != Shader::SPT_uint && + ptr_data._type != Shader::SPT_int) { GLCAT.error() << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n"; @@ -2205,10 +2205,10 @@ issue_parameters(int altered) { } else { switch (spec._dim[1]) { - case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; - case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue; + case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; + case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data._ptr); continue; } nassertd(false) continue; } @@ -3206,6 +3206,10 @@ glsl_compile_and_link() { valid &= glsl_compile_shader(Shader::ST_compute); } + if (!valid) { + return false; + } + // There might be warnings, so report those. GLSLShaders::const_iterator // it; for (it = _glsl_shaders.begin(); it != _glsl_shaders.end(); ++it) { // glsl_report_shader_errors(*it); } diff --git a/panda/src/glstuff/glmisc_src.cxx b/panda/src/glstuff/glmisc_src.cxx index 08e200a4a0..49eaf78a69 100644 --- a/panda/src/glstuff/glmisc_src.cxx +++ b/panda/src/glstuff/glmisc_src.cxx @@ -17,6 +17,11 @@ ConfigVariableInt gl_version ("gl-version", "", PRC_DESC("Set this to get an OpenGL context with a specific version.")); +ConfigVariableBool gl_forward_compatible + ("gl-forward-compatible", false, + PRC_DESC("Setting this to true will request a forward-compatible OpenGL " + "context, which will not support the fixed-function pipeline.")); + ConfigVariableBool gl_support_fbo ("gl-support-fbo", true, PRC_DESC("Configure this false if your GL's implementation of " diff --git a/panda/src/glstuff/glmisc_src.h b/panda/src/glstuff/glmisc_src.h index 3c6dd81e7b..415286c674 100644 --- a/panda/src/glstuff/glmisc_src.h +++ b/panda/src/glstuff/glmisc_src.h @@ -41,6 +41,7 @@ // #define GSG_VERBOSE 1 extern EXPCL_GL ConfigVariableInt gl_version; +extern EXPCL_GL ConfigVariableBool gl_forward_compatible; extern EXPCL_GL ConfigVariableBool gl_support_fbo; extern ConfigVariableBool gl_cheap_textures; extern ConfigVariableBool gl_ignore_clamp; diff --git a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx index d620427afa..83ea5f2b2f 100644 --- a/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx +++ b/panda/src/glxdisplay/glxGraphicsStateGuardian.cxx @@ -352,9 +352,20 @@ choose_pixel_format(const FrameBufferProperties &properties, attrib_list[n++] = gl_version[1]; } } + int flags = 0; if (gl_debug) { + flags |= GLX_CONTEXT_DEBUG_BIT_ARB; + } + if (gl_forward_compatible) { + flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; + if (gl_version.get_num_words() == 0 || gl_version[0] < 2) { + glxdisplay_cat.error() + << "gl-forward-compatible requires gl-version >= 3 0\n"; + } + } + if (flags != 0) { attrib_list[n++] = GLX_CONTEXT_FLAGS_ARB; - attrib_list[n++] = GLX_CONTEXT_DEBUG_BIT_ARB; + attrib_list[n++] = flags; } attrib_list[n] = None; _context = _glXCreateContextAttribs(_display, _fbconfig, _share_context, diff --git a/panda/src/linmath/lmatrix4_src.I b/panda/src/linmath/lmatrix4_src.I index a887d393f1..fe8ba22bb7 100644 --- a/panda/src/linmath/lmatrix4_src.I +++ b/panda/src/linmath/lmatrix4_src.I @@ -484,13 +484,9 @@ get_col(int col) const { */ INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4):: get_row3(int row) const { -#ifdef HAVE_EIGEN - return FLOATNAME(LVecBase3)(_m.block<1, 3>(row, 0)); -#else return FLOATNAME(LVecBase3)((*this)(row, 0), (*this)(row, 1), (*this)(row, 2)); -#endif // HAVE_EIGEN } /** @@ -514,13 +510,9 @@ get_row3(FLOATNAME(LVecBase3) &result_vec,int row) const { */ INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4):: get_col3(int col) const { -#ifdef HAVE_EIGEN - return FLOATNAME(LVecBase3)(_m.block<1, 3>(0, col)); -#else return FLOATNAME(LVecBase3)((*this)(0, col), (*this)(1, col), (*this)(2, col)); -#endif // HAVE_EIGEN } /** diff --git a/panda/src/mathutil/perlinNoise2.cxx b/panda/src/mathutil/perlinNoise2.cxx index ef187beb9c..1dc576e08a 100644 --- a/panda/src/mathutil/perlinNoise2.cxx +++ b/panda/src/mathutil/perlinNoise2.cxx @@ -19,6 +19,9 @@ */ double PerlinNoise2:: noise(const LVecBase2d &value) const { + // If this triggers, you passed in 0 for table_size. + nassertr(!_index.empty(), make_nan(0.0)); + // Convert the vector to our local coordinate space. LVecBase2d vec = _input_xform.xform_point(value); @@ -41,9 +44,13 @@ noise(const LVecBase2d &value) const { double v = fade(y); // Hash coordinates of the 4 square corners (A, B, A + 1, and B + 1) + nassertr(X >= 0 && X + 1 < _index.size(), make_nan(0.0)); int A = _index[X] + Y; int B = _index[X + 1] + Y; + nassertr(A >= 0 && A + 1 < _index.size(), make_nan(0.0)); + nassertr(B >= 0 && B + 1 < _index.size(), make_nan(0.0)); + // and add blended results from 4 corners of square. double result = lerp(v, lerp(u, grad(_index[A], x, y), diff --git a/panda/src/movies/flacAudioCursor.cxx b/panda/src/movies/flacAudioCursor.cxx index 0e3bbb163a..1c9d06984b 100644 --- a/panda/src/movies/flacAudioCursor.cxx +++ b/panda/src/movies/flacAudioCursor.cxx @@ -118,8 +118,10 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void FlacAudioCursor:: +int FlacAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; - _samples_read += drflac_read_s16(_drflac, desired, data) / _audio_channels; + n = drflac_read_s16(_drflac, desired, data) / _audio_channels; + _samples_read += n; + return n; } diff --git a/panda/src/movies/flacAudioCursor.h b/panda/src/movies/flacAudioCursor.h index 995be42e1e..93a48e5f32 100644 --- a/panda/src/movies/flacAudioCursor.h +++ b/panda/src/movies/flacAudioCursor.h @@ -37,7 +37,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; diff --git a/panda/src/movies/microphoneAudioDS.cxx b/panda/src/movies/microphoneAudioDS.cxx index 1ce78b0209..c04da23c3e 100644 --- a/panda/src/movies/microphoneAudioDS.cxx +++ b/panda/src/movies/microphoneAudioDS.cxx @@ -91,7 +91,7 @@ public: int _samples_per_buffer; public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); virtual int ready() const; public: @@ -323,7 +323,7 @@ MicrophoneAudioCursorDS:: /** * */ -void MicrophoneAudioCursorDS:: +int MicrophoneAudioCursorDS:: read_samples(int n, int16_t *data) { int orign = n; if (_handle) { @@ -373,6 +373,7 @@ read_samples(int n, int16_t *data) { if (n > 0) { memcpy(data, 0, n*2*_audio_channels); } + return orign - n; } /** diff --git a/panda/src/movies/movieAudioCursor.cxx b/panda/src/movies/movieAudioCursor.cxx index 33acc0e10c..a836937455 100644 --- a/panda/src/movies/movieAudioCursor.cxx +++ b/panda/src/movies/movieAudioCursor.cxx @@ -45,14 +45,14 @@ MovieAudioCursor:: * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void MovieAudioCursor:: +int MovieAudioCursor:: read_samples(int n, int16_t *data) { // This is the null implementation, which generates pure silence. Normally, // this method will be overridden by a subclass. if (n <= 0) { - return; + return 0; } int desired = n * _audio_channels; @@ -60,6 +60,7 @@ read_samples(int n, int16_t *data) { data[i] = 0; } _samples_read += n; + return n; } /** @@ -92,23 +93,28 @@ read_samples(int n, Datagram *dg) { * This is not particularly efficient, but it may be a convenient way to * manipulate samples in python. */ -std::string MovieAudioCursor:: +vector_uchar MovieAudioCursor:: read_samples(int n) { - std::ostringstream result; + vector_uchar result; int16_t tmp[4096]; while (n > 0) { int blocksize = (4096 / _audio_channels); - if (blocksize > n) blocksize = n; - int words = blocksize * _audio_channels; - read_samples(blocksize, tmp); - for (int i=0; i>8) & 255)); + if (blocksize > n) { + blocksize = n; } - n -= blocksize; + int nread = read_samples(blocksize, tmp); + if (nread == 0) { + return result; + } + int words = nread * _audio_channels; + for (int i = 0; i < words; ++i) { + int16_t word = tmp[i]; + result.push_back((uint8_t)(word & 255u)); + result.push_back((uint8_t)((word >> 8) & 255u)); + } + n -= nread; } - return result.str(); + return result; } diff --git a/panda/src/movies/movieAudioCursor.h b/panda/src/movies/movieAudioCursor.h index b2d5d9bebe..e7c9668713 100644 --- a/panda/src/movies/movieAudioCursor.h +++ b/panda/src/movies/movieAudioCursor.h @@ -48,10 +48,10 @@ PUBLISHED: virtual int ready() const; virtual void seek(double offset); void read_samples(int n, Datagram *dg); - std::string read_samples(int n); + vector_uchar read_samples(int n); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); protected: PT(MovieAudio) _source; diff --git a/panda/src/movies/movieTypeRegistry.cxx b/panda/src/movies/movieTypeRegistry.cxx index 5bc8228bb4..dca0ef9400 100644 --- a/panda/src/movies/movieTypeRegistry.cxx +++ b/panda/src/movies/movieTypeRegistry.cxx @@ -31,6 +31,12 @@ PT(MovieAudio) MovieTypeRegistry:: make_audio(const Filename &name) { string ext = downcase(name.get_extension()); +#ifdef HAVE_ZLIB + if (ext == "pz" || ext == "gz") { + ext = Filename(name.get_basename_wo_extension()).get_extension(); + } +#endif + _audio_lock.lock(); // Make sure that the list of audio types has been read in. @@ -154,6 +160,12 @@ PT(MovieVideo) MovieTypeRegistry:: make_video(const Filename &name) { string ext = downcase(name.get_extension()); +#ifdef HAVE_ZLIB + if (ext == "pz" || ext == "gz") { + ext = Filename(name.get_basename_wo_extension()).get_extension(); + } +#endif + _video_lock.lock(); // Make sure that the list of video types has been read in. diff --git a/panda/src/movies/opusAudioCursor.cxx b/panda/src/movies/opusAudioCursor.cxx index 8554215088..ab81068742 100644 --- a/panda/src/movies/opusAudioCursor.cxx +++ b/panda/src/movies/opusAudioCursor.cxx @@ -56,6 +56,22 @@ int cb_seek(void *stream, opus_int64 offset, int whence) { break; case SEEK_CUR: + // opusfile uses a seek with offset 0 to determine whether seeking is + // supported, but this is not good enough. We seek to the end and back. + if (offset == 0) { + std::streambuf *buf = in->rdbuf(); + std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (pos < 0) { + return -1; + } + if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) { + // It worked; seek back to the previous location. + buf->pubseekpos(pos, std::ios::in); + return 0; + } else { + return -1; + } + } in->seekg(offset, std::ios::cur); break; @@ -101,16 +117,7 @@ opus_int64 cb_tell(void *stream) { return in->tellg(); } -int cb_close(void *stream) { - istream *in = (istream *)stream; - nassertr(in != nullptr, EOF); - - VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); - vfs->close_read_file(in); - return 0; -} - -static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close}; +static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, nullptr}; TypeHandle OpusAudioCursor::_type_handle; @@ -122,6 +129,7 @@ OpusAudioCursor:: OpusAudioCursor(OpusAudio *src, istream *stream) : MovieAudioCursor(src), _is_valid(false), + _stream(stream), _link(0) { nassertv(stream != nullptr); @@ -159,6 +167,11 @@ OpusAudioCursor:: op_free(_op); _op = nullptr; } + + if (_stream != nullptr) { + VirtualFileSystem::close_read_file(_stream); + _stream = nullptr; + } } /** @@ -174,7 +187,32 @@ seek(double t) { t = std::max(t, 0.0); // Use op_time_seek_lap if cross-lapping is enabled. - int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0)); + ogg_int64_t sample = (ogg_int64_t)(t * 48000.0); + int error = op_pcm_seek(_op, sample); + + // Special case for seeking to the beginning; if normal seek fails, we may + // be able to explicitly seek to the beginning of the file and call op_open + // again. This allows looping compressed .opus files. + if (error == OP_ENOSEEK && sample == 0) { + if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { + OggOpusFile *op = op_open_callbacks((void *)_stream, &callbacks, nullptr, 0, nullptr); + if (op != nullptr) { + op_free(_op); + _op = op; + } else { + movies_cat.error() + << "Failed to reopen Opus file to seek to beginning.\n"; + return; + } + + // Reset this field for good measure, just in case this changed. + _audio_channels = op_channel_count(_op, -1); + + _last_seek = 0.0; + _samples_read = 0; + return; + } + } if (error != 0) { movies_cat.error() << "Seek failed (error " << error << "). Opus stream may not be seekable.\n"; @@ -190,7 +228,7 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void OpusAudioCursor:: +int OpusAudioCursor:: read_samples(int n, int16_t *data) { int16_t *end = data + (n * _audio_channels); @@ -202,6 +240,9 @@ read_samples(int n, int16_t *data) { data += read_samples * _audio_channels; _samples_read += read_samples; } else { + if (read_samples == 0 && _length == 1.0E10) { + _length = op_pcm_tell(_op) / 48000.0; + } break; } @@ -224,7 +265,9 @@ read_samples(int n, int16_t *data) { // Fill the rest of the buffer with silence. if (data < end) { memset(data, 0, (unsigned char *)end - (unsigned char *)data); + n -= (end - data) / _audio_channels; } + return n; } #endif // HAVE_OPUS diff --git a/panda/src/movies/opusAudioCursor.h b/panda/src/movies/opusAudioCursor.h index b3996c73bb..3e2137de5e 100644 --- a/panda/src/movies/opusAudioCursor.h +++ b/panda/src/movies/opusAudioCursor.h @@ -39,22 +39,14 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; protected: OggOpusFile *_op; - + std::istream *_stream; int _link; - double _byte_rate; - int _block_align; - int _bytes_per_sample; - bool _is_float; - - std::streampos _data_start; - std::streampos _data_pos; - size_t _data_size; public: static TypeHandle get_class_type() { diff --git a/panda/src/movies/userDataAudio.cxx b/panda/src/movies/userDataAudio.cxx index f02726d10b..9490432dc0 100644 --- a/panda/src/movies/userDataAudio.cxx +++ b/panda/src/movies/userDataAudio.cxx @@ -57,12 +57,14 @@ open() { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void UserDataAudio:: +int UserDataAudio:: read_samples(int n, int16_t *data) { int ready = (_data.size() / _desired_channels); int desired = n * _desired_channels; - int avail = ready * _desired_channels; - if (avail > desired) avail = desired; + if (n > ready) { + n = ready; + } + int avail = n * _desired_channels; for (int i=0; i_remove_after_read) { - source->read_samples(n, data); + if (source->_remove_after_read) { + n = source->read_samples(n, data); } else { int offset = _samples_read * _audio_channels; @@ -66,9 +66,12 @@ read_samples(int n, int16_t *data) { for (int i=avail; irdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) { + // Back up the callbacks, then destroy the stream, making sure to first + // unset the datasource so that it won't close the file. + ov_callbacks callbacks = _ov.callbacks; + _ov.datasource = nullptr; + ov_clear(&_ov); + + if (ov_open_callbacks((void *)stream, &_ov, nullptr, 0, callbacks) != 0) { + movies_cat.error() + << "Failed to reopen Ogg Vorbis file to seek to beginning.\n"; + return; + } + + // Reset these fields for good measure, just in case the file changed. + vorbis_info *vi = ov_info(&_ov, -1); + _audio_channels = vi->channels; + _audio_rate = vi->rate; + + _last_seek = 0.0; + _samples_read = 0; return; } } + if (result != 0) { + movies_cat.error() + << "Seek failed. Ogg Vorbis stream may not be seekable.\n"; + } _last_seek = ov_time_tell(&_ov); _samples_read = 0; @@ -114,7 +141,7 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void VorbisAudioCursor:: +int VorbisAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; @@ -131,6 +158,9 @@ read_samples(int n, int16_t *data) { buffer += read_bytes; length -= read_bytes; } else { + if (read_bytes == 0 && _length == 1.0E10) { + _length = ov_time_tell(&_ov); + } break; } @@ -159,6 +189,7 @@ read_samples(int n, int16_t *data) { } _samples_read += n; + return n; } /** @@ -199,6 +230,22 @@ cb_seek_func(void *datasource, ogg_int64_t offset, int whence) { break; case SEEK_CUR: + // Vorbis uses a seek with offset 0 to determine whether seeking is + // supported, but this is not good enough. We seek to the end and back. + if (offset == 0) { + std::streambuf *buf = stream->rdbuf(); + std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (pos < 0) { + return -1; + } + if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) { + // It worked; seek back to the previous location. + buf->pubseekpos(pos, std::ios::in); + return 0; + } else { + return -1; + } + } stream->seekg(offset, std::ios::cur); break; diff --git a/panda/src/movies/vorbisAudioCursor.h b/panda/src/movies/vorbisAudioCursor.h index d194cb0c23..cd74acc750 100644 --- a/panda/src/movies/vorbisAudioCursor.h +++ b/panda/src/movies/vorbisAudioCursor.h @@ -35,7 +35,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; @@ -50,16 +50,7 @@ protected: #ifndef CPPPARSER OggVorbis_File _ov; #endif - int _bitstream; - double _byte_rate; - int _block_align; - int _bytes_per_sample; - bool _is_float; - - std::streampos _data_start; - std::streampos _data_pos; - size_t _data_size; public: static TypeHandle get_class_type() { diff --git a/panda/src/movies/wavAudioCursor.cxx b/panda/src/movies/wavAudioCursor.cxx index 381ce2d81b..036588c189 100644 --- a/panda/src/movies/wavAudioCursor.cxx +++ b/panda/src/movies/wavAudioCursor.cxx @@ -294,27 +294,61 @@ seek(double t) { t = std::max(t, 0.0); std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size); + std::streambuf *buf = _stream->rdbuf(); + if (_can_seek_fast) { - _stream->seekg(pos); - if (_stream->tellg() != pos) { + if (buf->pubseekpos(pos, std::ios::in) != pos) { // Clearly, we can't seek fast. Fall back to the case below. _can_seek_fast = false; } } - if (!_can_seek_fast) { - std::streampos current = _stream->tellg(); + // Get the current position of the cursor in the file. + std::streampos current = buf->pubseekoff(0, std::ios::cur, std::ios::in); + if (!_can_seek_fast) { if (pos > current) { // It is ahead of our current position. Skip ahead. - _reader.skip_bytes(pos - current); + _stream->ignore(pos - current); + current = pos; } else if (pos < current) { - // We'll have to reopen the file. TODO + // Can we seek to the beginning? Some streams, such as ZStream, let us + // rewind the stream. + if (buf->pubseekpos(0, std::ios::in) == (std::streampos)0) { + if (pos > _data_start && movies_cat.is_info()) { + Filename fn = get_source()->get_filename(); + movies_cat.info() + << "Unable to seek backwards in " << fn.get_basename() + << "; seeking to beginning and skipping " << pos << " bytes.\n"; + } + _stream->ignore(pos); + current = pos; + } else { + // No; close and reopen the file. + Filename fn = get_source()->get_filename(); + movies_cat.warning() + << "Unable to seek backwards in " << fn.get_basename() + << "; reopening and skipping " << pos << " bytes.\n"; + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *stream = vfs->open_read_file(get_source()->get_filename(), true); + if (stream != nullptr) { + vfs->close_read_file(_stream); + stream->ignore(pos); + _stream = stream; + _reader = StreamReader(stream, false); + current = pos; + } else { + movies_cat.error() + << "Unable to reopen " << fn << ".\n"; + _can_seek = false; + } + } } } - _data_pos = _stream->tellg() - _data_start; + _data_pos = (size_t)current - _data_start; _last_seek = _data_pos / _byte_rate; _samples_read = 0; } @@ -324,13 +358,13 @@ seek(double t) { * read. Your buffer must be equal in size to N * channels. Multiple-channel * audio will be interleaved. */ -void WavAudioCursor:: +int WavAudioCursor:: read_samples(int n, int16_t *data) { int desired = n * _audio_channels; int read_samples = std::min(desired, ((int) (_data_size - _data_pos)) / _bytes_per_sample); if (read_samples <= 0) { - return; + return 0; } switch (_format) { @@ -421,8 +455,10 @@ read_samples(int n, int16_t *data) { // Fill the rest of the buffer with silence. if (read_samples < desired) { memset(data + read_samples, 0, (desired - read_samples) * 2); + n = read_samples / _audio_channels; } _data_pos = _stream->tellg() - _data_start; - _samples_read += read_samples / _audio_channels; + _samples_read += n; + return n; } diff --git a/panda/src/movies/wavAudioCursor.h b/panda/src/movies/wavAudioCursor.h index 21133b1c62..9ae03a5d7d 100644 --- a/panda/src/movies/wavAudioCursor.h +++ b/panda/src/movies/wavAudioCursor.h @@ -31,7 +31,7 @@ PUBLISHED: virtual void seek(double offset); public: - virtual void read_samples(int n, int16_t *data); + virtual int read_samples(int n, int16_t *data); bool _is_valid; diff --git a/panda/src/ode/odeBody.h b/panda/src/ode/odeBody.h index f1cd9dae68..5cdfd4c8d1 100644 --- a/panda/src/ode/odeBody.h +++ b/panda/src/ode/odeBody.h @@ -133,6 +133,7 @@ PUBLISHED: OdeJoint get_joint(int index) const; MAKE_SEQ(get_joints, get_num_joints, get_joint); EXTENSION(INLINE PyObject *get_converted_joint(int i) const); + MAKE_SEQ_PROPERTY(joints, get_num_joints, get_converted_joint); INLINE void enable(); INLINE void disable(); diff --git a/panda/src/ode/odeJoint.h b/panda/src/ode/odeJoint.h index eda2a98b96..47233a9361 100644 --- a/panda/src/ode/odeJoint.h +++ b/panda/src/ode/odeJoint.h @@ -83,7 +83,7 @@ PUBLISHED: INLINE void set_feedback(bool flag = true); INLINE OdeJointFeedback *get_feedback(); - EXTENSION(void attach(const OdeBody *body1, const OdeBody *body2)); + EXTENSION(void attach(PyObject *body1, PyObject *body2)); void attach_bodies(const OdeBody &body1, const OdeBody &body2); void attach_body(const OdeBody &body, int index); void detach(); diff --git a/panda/src/ode/odeJoint_ext.cxx b/panda/src/ode/odeJoint_ext.cxx index 6f284e1213..57669b7644 100644 --- a/panda/src/ode/odeJoint_ext.cxx +++ b/panda/src/ode/odeJoint_ext.cxx @@ -29,6 +29,7 @@ #include "odePlane2dJoint.h" #ifndef CPPPARSER +extern Dtool_PyTypedObject Dtool_OdeBody; extern Dtool_PyTypedObject Dtool_OdeJoint; extern Dtool_PyTypedObject Dtool_OdeBallJoint; extern Dtool_PyTypedObject Dtool_OdeHingeJoint; @@ -48,7 +49,23 @@ extern Dtool_PyTypedObject Dtool_OdePlane2dJoint; * attached to the environment. */ void Extension:: -attach(const OdeBody *body1, const OdeBody *body2) { +attach(PyObject *param1, PyObject *param2) { + const OdeBody *body1 = nullptr; + if (param1 != Py_None) { + body1 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param1, &Dtool_OdeBody, 1, "OdeJoint.attach", true, true); + if (body1 == nullptr) { + return; + } + } + + const OdeBody *body2 = nullptr; + if (param2 != Py_None) { + body2 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param2, &Dtool_OdeBody, 2, "OdeJoint.attach", true, true); + if (body2 == nullptr) { + return; + } + } + if (body1 && body2) { _this->attach_bodies(*body1, *body2); diff --git a/panda/src/ode/odeJoint_ext.h b/panda/src/ode/odeJoint_ext.h index b61938506f..84dbf4ac93 100644 --- a/panda/src/ode/odeJoint_ext.h +++ b/panda/src/ode/odeJoint_ext.h @@ -30,7 +30,7 @@ template<> class Extension : public ExtensionBase { public: - void attach(const OdeBody *body1, const OdeBody *body2); + void attach(PyObject *body1, PyObject *body2); PyObject *convert() const; }; diff --git a/panda/src/pgraph/shaderAttrib.cxx b/panda/src/pgraph/shaderAttrib.cxx index 24b245b218..934773ad49 100644 --- a/panda/src/pgraph/shaderAttrib.cxx +++ b/panda/src/pgraph/shaderAttrib.cxx @@ -422,6 +422,57 @@ get_shader_input_ptr(const InternalName *id) const { } } +/** + * Returns the ShaderInput as a ShaderPtrData struct. Assertion fails if + * there is none. or if it is not a PTA(double/float) + */ +bool ShaderAttrib:: +get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const { + Inputs::const_iterator i = _inputs.find(id); + if (i != _inputs.end()) { + const ShaderInput &p = (*i).second; + if (p.get_value_type() == ShaderInput::M_numeric || + p.get_value_type() == ShaderInput::M_vector) { + + data = p.get_ptr(); + return (data._ptr != nullptr); + } + if (p.get_value_type() == ShaderInput::M_param) { + // Temporary solution until the new param system + TypedWritableReferenceCount *param = p.get_value(); + if (param != nullptr) { + if (param->is_of_type(ParamVecBase4f::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4f *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_float; + return true; + } + else if (param->is_of_type(ParamVecBase4i::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4i *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_int; + return true; + } + else if (param->is_of_type(ParamVecBase4d::get_class_type())) { + data._ptr = (void *)((const ParamVecBase4d *)param)->get_value().get_data(); + data._size = 4; + data._type = Shader::SPT_float; + return true; + } + } + } + ostringstream strm; + strm << "Shader input " << id->get_name() << " was given an incompatible parameter type.\n"; + nassert_raise(strm.str()); + return false; + } else { + ostringstream strm; + strm << "Shader input " << id->get_name() << " is not present.\n"; + nassert_raise(strm.str()); + return false; + } +} + /** * Returns the ShaderInput as a texture. Assertion fails if there is none, or * if it is not a texture. diff --git a/panda/src/pgraph/shaderAttrib.h b/panda/src/pgraph/shaderAttrib.h index 29b535ff8a..fb0cc6e996 100644 --- a/panda/src/pgraph/shaderAttrib.h +++ b/panda/src/pgraph/shaderAttrib.h @@ -119,6 +119,7 @@ PUBLISHED: LVecBase4 get_shader_input_vector(InternalName *id) const; Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=nullptr) const; const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const; + bool get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const; const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const; ShaderBuffer *get_shader_input_buffer(const InternalName *id) const; diff --git a/panda/src/pgui/pgButton.cxx b/panda/src/pgui/pgButton.cxx index d1eba186c0..09a91120d4 100644 --- a/panda/src/pgui/pgButton.cxx +++ b/panda/src/pgui/pgButton.cxx @@ -115,7 +115,11 @@ release(const MouseWatcherParameter ¶m, bool background) { if (has_click_button(param.get_button())) { _button_down = false; if (get_active()) { - if (param.is_outside()) { + // Note that a "click" may come from a keyboard button press. In that + // case, instead of checking that the mouse cursor is still over the + // button, we check whether the item has keyboard focus. + if (param.is_outside() && + (MouseButton::is_mouse_button(param.get_button()) || !get_focus())) { set_state(S_ready); } else { set_state(S_rollover); diff --git a/panda/src/pipeline/mutexDebug.I b/panda/src/pipeline/mutexDebug.I index ae3ce74967..3259f2aa67 100644 --- a/panda/src/pipeline/mutexDebug.I +++ b/panda/src/pipeline/mutexDebug.I @@ -70,6 +70,8 @@ acquire(Thread *current_thread) const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool MutexDebug:: try_acquire(Thread *current_thread) const { diff --git a/panda/src/pipeline/mutexDirect.I b/panda/src/pipeline/mutexDirect.I index 71a26543a7..3daf53a179 100644 --- a/panda/src/pipeline/mutexDirect.I +++ b/panda/src/pipeline/mutexDirect.I @@ -60,6 +60,8 @@ acquire() const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool MutexDirect:: try_acquire() const { diff --git a/panda/src/pipeline/pmutex.h b/panda/src/pipeline/pmutex.h index 2a47b7dbac..51b588a0c9 100644 --- a/panda/src/pipeline/pmutex.h +++ b/panda/src/pipeline/pmutex.h @@ -49,6 +49,10 @@ PUBLISHED: void operator = (const Mutex ©) = delete; + EXTENSION(bool acquire(bool blocking=true) const); + EXTENSION(bool __enter__()); + EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *)); + public: // This is a global mutex set aside for the purpose of protecting Notify // messages from being interleaved between threads. diff --git a/panda/src/pipeline/pmutex_ext.I b/panda/src/pipeline/pmutex_ext.I new file mode 100644 index 0000000000..5f3ba1fbe2 --- /dev/null +++ b/panda/src/pipeline/pmutex_ext.I @@ -0,0 +1,53 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +acquire(bool blocking) const { + if (_this->try_lock()) { + return true; + } + + if (!blocking) { + return false; + } + + // Release the GIL while we are waiting for the lock. +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + _this->lock(); + Py_BLOCK_THREADS +#else + _this->lock(); +#endif + return true; +} + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +__enter__() { + return acquire(true); +} + +/** + * Releases the mutex. + */ +INLINE void Extension:: +__exit__(PyObject *, PyObject *, PyObject *) { + _this->unlock(); +} diff --git a/panda/src/pipeline/pmutex_ext.h b/panda/src/pipeline/pmutex_ext.h new file mode 100644 index 0000000000..427d0c87f6 --- /dev/null +++ b/panda/src/pipeline/pmutex_ext.h @@ -0,0 +1,41 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +#ifndef PMUTEX_EXT_H +#define PMUTEX_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "pmutex.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for Mutex, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + INLINE bool acquire(bool blocking) const; + INLINE bool __enter__(); + INLINE void __exit__(PyObject *, PyObject *, PyObject *); +}; + +#include "pmutex_ext.I" + +#endif // HAVE_PYTHON + +#endif // PMUTEX_EXT_H diff --git a/panda/src/pipeline/reMutex.h b/panda/src/pipeline/reMutex.h index bdf9031304..bb87953949 100644 --- a/panda/src/pipeline/reMutex.h +++ b/panda/src/pipeline/reMutex.h @@ -42,6 +42,10 @@ PUBLISHED: ~ReMutex() = default; void operator = (const ReMutex ©) = delete; + + EXTENSION(bool acquire(bool blocking=true) const); + EXTENSION(bool __enter__()); + EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *)); }; #include "reMutex.I" diff --git a/panda/src/pipeline/reMutexDirect.I b/panda/src/pipeline/reMutexDirect.I index e7fa3a6fce..4e2fdb9abb 100644 --- a/panda/src/pipeline/reMutexDirect.I +++ b/panda/src/pipeline/reMutexDirect.I @@ -105,6 +105,8 @@ acquire(Thread *current_thread) const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool ReMutexDirect:: try_acquire() const { @@ -119,6 +121,8 @@ try_acquire() const { /** * Returns immediately, with a true value indicating the mutex has been * acquired, and false indicating it has not. + * + * @deprecated Python users should use acquire(False), C++ users try_lock() */ INLINE bool ReMutexDirect:: try_acquire(Thread *current_thread) const { diff --git a/panda/src/pipeline/reMutex_ext.I b/panda/src/pipeline/reMutex_ext.I new file mode 100644 index 0000000000..8c28108f4c --- /dev/null +++ b/panda/src/pipeline/reMutex_ext.I @@ -0,0 +1,53 @@ +/** + * 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 pmutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +acquire(bool blocking) const { + if (_this->try_lock()) { + return true; + } + + if (!blocking) { + return false; + } + + // Release the GIL while we are waiting for the lock. +#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS) + PyThreadState *_save; + Py_UNBLOCK_THREADS + _this->lock(); + Py_BLOCK_THREADS +#else + _this->lock(); +#endif + return true; +} + +/** + * Acquires the mutex. + */ +INLINE bool Extension:: +__enter__() { + return acquire(true); +} + +/** + * Releases the mutex. + */ +INLINE void Extension:: +__exit__(PyObject *, PyObject *, PyObject *) { + _this->unlock(); +} diff --git a/panda/src/pipeline/reMutex_ext.h b/panda/src/pipeline/reMutex_ext.h new file mode 100644 index 0000000000..72eb50305d --- /dev/null +++ b/panda/src/pipeline/reMutex_ext.h @@ -0,0 +1,41 @@ +/** + * 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 remutex_ext.h + * @author rdb + * @date 2019-05-12 + */ + +#ifndef REMUTEX_EXT_H +#define REMUTEX_EXT_H + +#include "dtoolbase.h" + +#ifdef HAVE_PYTHON + +#include "extension.h" +#include "reMutex.h" +#include "py_panda.h" + +/** + * This class defines the extension methods for ReMutex, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + INLINE bool acquire(bool blocking) const; + INLINE bool __enter__(); + INLINE void __exit__(PyObject *, PyObject *, PyObject *); +}; + +#include "reMutex_ext.I" + +#endif // HAVE_PYTHON + +#endif // REMUTEX_EXT_H diff --git a/panda/src/putil/sparseArray.cxx b/panda/src/putil/sparseArray.cxx index 6373e53039..5404728aa1 100644 --- a/panda/src/putil/sparseArray.cxx +++ b/panda/src/putil/sparseArray.cxx @@ -262,8 +262,8 @@ compare_to(const SparseArray &other) const { return -1; } - --ai; - --bi; + ++ai; + ++bi; } if (ai != _subranges.rend()) { @@ -440,9 +440,9 @@ do_remove_range(int begin, int end) { if (si == _subranges.end()) { if (!_subranges.empty()) { si = _subranges.begin() + _subranges.size() - 1; - if ((*si)._end >= begin) { + if ((*si)._end > begin) { // The new range shortens the last element of the array on the right. - end = std::min(end, (*si)._begin); + end = std::max(begin, (*si)._begin); (*si)._end = end; // It might also shorten it on the left; fall through. } else { @@ -462,10 +462,10 @@ do_remove_range(int begin, int end) { if (si != _subranges.begin()) { Subranges::iterator si2 = si; --si2; - if ((*si2)._end >= begin) { + if ((*si2)._end > begin) { // The new range shortens an element within the array on the right // (but does not intersect the next element). - end = std::min(end, (*si2)._begin); + end = std::max(begin, (*si2)._begin); (*si2)._end = end; // It might also shorten it on the left; fall through. si = si2; @@ -488,7 +488,7 @@ do_remove_range(int begin, int end) { } // Check if the new range removes any elements to the left. - while (begin <= (*si)._begin) { + while (begin <= (*si)._begin || (*si)._begin >= (*si)._end) { if (si == _subranges.begin()) { _subranges.erase(si); return; @@ -500,6 +500,7 @@ do_remove_range(int begin, int end) { } (*si)._end = std::min((*si)._end, begin); + nassertv((*si)._end > (*si)._begin); } /** diff --git a/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx b/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx index c98f12cf48..ed9b0e0b2c 100644 --- a/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx +++ b/panda/src/wgldisplay/wglGraphicsStateGuardian.cxx @@ -609,9 +609,20 @@ make_context(HDC hdc) { attrib_list[n++] = gl_version[1]; } } + int flags = 0; if (gl_debug) { + flags |= WGL_CONTEXT_DEBUG_BIT_ARB; + } + if (gl_forward_compatible) { + flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; + if (gl_version.get_num_words() == 0 || gl_version[0] < 2) { + wgldisplay_cat.error() + << "gl-forward-compatible requires gl-version >= 3 0\n"; + } + } + if (flags != 0) { attrib_list[n++] = WGL_CONTEXT_FLAGS_ARB; - attrib_list[n++] = WGL_CONTEXT_DEBUG_BIT_ARB; + attrib_list[n++] = flags; } #ifndef SUPPORT_FIXED_FUNCTION attrib_list[n++] = WGL_CONTEXT_PROFILE_MASK_ARB; diff --git a/panda/src/windisplay/config_windisplay.cxx b/panda/src/windisplay/config_windisplay.cxx index 6e0ea2406d..62040c553a 100644 --- a/panda/src/windisplay/config_windisplay.cxx +++ b/panda/src/windisplay/config_windisplay.cxx @@ -86,6 +86,11 @@ ConfigVariableBool swapbuffer_framelock ("swapbuffer-framelock", false, PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards")); +ConfigVariableBool paste_emit_keystrokes +("paste-emit-keystrokes", true, + PRC_DESC("Handle paste events (Ctrl-V) as separate keystroke events for each " + "pasted character.")); + /** * Initializes the library. This must be called at least once before any of * the functions or classes in this library can be used. Normally it will be diff --git a/panda/src/windisplay/config_windisplay.h b/panda/src/windisplay/config_windisplay.h index 3256287f5b..7199b8603d 100644 --- a/panda/src/windisplay/config_windisplay.h +++ b/panda/src/windisplay/config_windisplay.h @@ -31,6 +31,7 @@ extern ConfigVariableBool ime_hide; extern ConfigVariableBool request_dxdisplay_information; extern ConfigVariableBool dpi_aware; extern ConfigVariableBool dpi_window_resize; +extern ConfigVariableBool paste_emit_keystrokes; extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index d3e444cac9..c2e98beb0e 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -1927,7 +1927,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // Handle Cntrl-V paste from clipboard. Is there a better way to detect // this hotkey? if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) && - !_input_devices.empty()) { + !_input_devices.empty() && paste_emit_keystrokes) { HGLOBAL hglb; char *lptstr; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 5599959cdb..0a5d969bdf 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -589,7 +589,13 @@ set_properties_now(WindowProperties &properties) { // OK, first figure out which CRTC the window is on. It may be on more // than one, actually, so grab a point in the center in order to figure // out which one it's more-or-less mostly on. - LPoint2i center = _properties.get_origin() + _properties.get_size() / 2; + LPoint2i center(0, 0); + if (_properties.has_origin()) { + center = _properties.get_origin(); + if (_properties.has_size()) { + center += _properties.get_size() / 2; + } + } int x, y, width, height; x11_pipe->find_fullscreen_crtc(center, x, y, width, height); @@ -628,7 +634,7 @@ set_properties_now(WindowProperties &properties) { // We may need to change the screen resolution. The code below is // suboptimal; in the future, we probably want to only touch the CRTC // that the window is on. - XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow); + XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root()); SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation); SizeID new_size_id = (SizeID) -1; int num_sizes = 0; @@ -1010,34 +1016,6 @@ open_window() { // Make sure we are not making X11 calls from other threads. LightReMutexHolder holder(x11GraphicsPipe::_x_mutex); - if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) { - XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root()); - if (_orig_size_id == (SizeID) -1) { - _orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation); - } - int num_sizes, new_size_id = -1; - XRRScreenSize *xrrs; - xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes); - for (int i = 0; i < num_sizes; ++i) { - if (xrrs[i].width == _properties.get_x_size() && - xrrs[i].height == _properties.get_y_size()) { - new_size_id = i; - } - } - if (new_size_id == -1) { - x11display_cat.error() - << "Videocard has no supported display resolutions at specified res (" - << _properties.get_x_size() << " x " << _properties.get_y_size() <<")\n"; - _orig_size_id = -1; - return false; - } - if (new_size_id != _orig_size_id) { - _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime); - } else { - _orig_size_id = -1; - } - } - X11_Window parent_window = x11_pipe->get_root(); WindowHandle *window_handle = _properties.get_parent_window(); if (window_handle != nullptr) { diff --git a/tests/bullet/test_bullet_heightfield.py b/tests/bullet/test_bullet_heightfield.py new file mode 100644 index 0000000000..28174b93ec --- /dev/null +++ b/tests/bullet/test_bullet_heightfield.py @@ -0,0 +1,45 @@ +import pytest +# Skip these tests if we can't import bullet. +bullet = pytest.importorskip("panda3d.bullet") + +from panda3d.bullet import BulletWorld, BulletRigidBodyNode, ZUp +from panda3d.bullet import BulletHeightfieldShape, BulletSphereShape +from panda3d.core import NodePath, PNMImage + + +def make_node(name, BulletShape, *args): + # Returns a BulletRigidBodyNode for the given shape + shape = BulletShape(*args) + node = BulletRigidBodyNode(name) + node.add_shape(shape) + return node + + +def test_sphere_into_heightfield(): + root = NodePath("root") + world = BulletWorld() + # Create PNMImage to construct Heightfield with + img = PNMImage(10, 10, 1) + img.fill_val(255) + # Make our nodes + heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp) + sphere = make_node("Sphere", BulletSphereShape, 1) + # Attach to world + np1 = root.attach_new_node(sphere) + np1.set_pos(0, 0, 1) + world.attach(sphere) + + np2 = root.attach_new_node(heightfield) + np2.set_pos(0, 0, 0) + world.attach(heightfield) + + assert world.get_num_rigid_bodies() == 2 + test = world.contact_test_pair(sphere, heightfield) + assert test.get_num_contacts() > 0 + assert test.get_contact(0).get_node0() == sphere + assert test.get_contact(0).get_node1() == heightfield + + # Increment sphere's Z coordinate, no longer colliding + np1.set_pos(0, 0, 2) + test = world.contact_test_pair(sphere, heightfield) + assert test.get_num_contacts() == 0 diff --git a/tests/display/test_glsl_shader.py b/tests/display/test_glsl_shader.py index 8deadf40d9..401166a26d 100644 --- a/tests/display/test_glsl_shader.py +++ b/tests/display/test_glsl_shader.py @@ -294,6 +294,36 @@ def test_glsl_pta_mat4(gsg): run_glsl_test(gsg, code, preamble, {'pta': pta}) +def test_glsl_param_vec4(gsg): + param = core.ParamVecBase4((0, 1, 2, 3)) + + preamble = """ + uniform vec4 param; + """ + code = """ + assert(param.x == 0.0); + assert(param.y == 1.0); + assert(param.z == 2.0); + assert(param.w == 3.0); + """ + run_glsl_test(gsg, code, preamble, {'param': param}) + + +def test_glsl_param_ivec4(gsg): + param = core.ParamVecBase4i((0, 1, 2, 3)) + + preamble = """ + uniform ivec4 param; + """ + code = """ + assert(param.x == 0); + assert(param.y == 1); + assert(param.z == 2); + assert(param.w == 3); + """ + run_glsl_test(gsg, code, preamble, {'param': param}) + + def test_glsl_write_extract_image_buffer(gsg): # Tests that we can write to a buffer texture on the GPU, and then extract # the data on the CPU. We test two textures since there was in the past a diff --git a/tests/dtoolutil/test_iostream.py b/tests/dtoolutil/test_iostream.py new file mode 100644 index 0000000000..2473ef80d2 --- /dev/null +++ b/tests/dtoolutil/test_iostream.py @@ -0,0 +1,128 @@ +from panda3d.core import StringStream + +import pytest + + +ISTREAM_DATA = b'abcdefghijklmnopqrstuvwxyz' * 500 + +@pytest.fixture +def istream(): + return StringStream(ISTREAM_DATA) + + +def test_istream_readall(istream): + assert istream.readall() == ISTREAM_DATA + assert istream.readall() == b'' + assert istream.readall() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read(istream): + assert istream.read() == ISTREAM_DATA + assert istream.read() == b'' + assert istream.read() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read_size(istream): + assert istream.read(100) == ISTREAM_DATA[:100] + assert istream.read(5000) == ISTREAM_DATA[100:5100] + assert istream.read(5000) == ISTREAM_DATA[5100:10100] + assert istream.read(5000) == ISTREAM_DATA[10100:15100] + assert istream.read() == b'' + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read1(istream): + accumulated = b'' + data = istream.read1() + while data: + accumulated += data + data = istream.read1() + + assert accumulated == ISTREAM_DATA + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_read1_size(istream): + accumulated = b'' + data = istream.read1(4000) + while data: + accumulated += data + data = istream.read1(4000) + + assert accumulated == ISTREAM_DATA + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_readinto(istream): + ba = bytearray() + assert istream.readinto(ba) == 0 + assert istream.tellg() == 0 + + ba = bytearray(10) + assert istream.readinto(ba) == 10 + assert ba == ISTREAM_DATA[:10] + assert istream.tellg() == 10 + + ba = bytearray(len(ISTREAM_DATA)) + assert istream.readinto(ba) == len(ISTREAM_DATA) - 10 + assert ba[:len(ISTREAM_DATA)-10] == ISTREAM_DATA[10:] + assert istream.tellg() == len(ISTREAM_DATA) + + +def test_istream_readline(): + # Empty stream + stream = StringStream(b'') + assert stream.readline() == b'' + assert stream.readline() == b'' + + # Single line without newline + stream = StringStream(b'A') + assert stream.readline() == b'A' + assert stream.readline() == b'' + + # Single newline + stream = StringStream(b'\n') + assert stream.readline() == b'\n' + assert stream.readline() == b'' + + # Line with text followed by empty line + stream = StringStream(b'A\n\n') + assert stream.readline() == b'A\n' + assert stream.readline() == b'\n' + assert stream.readline() == b'' + + # Preserve null byte + stream = StringStream(b'\x00\x00') + assert stream.readline() == b'\x00\x00' + + +def test_istream_readlines(): + istream = StringStream(b'a') + assert istream.readlines() == [b'a'] + assert istream.readlines() == [] + + istream = StringStream(b'a\nb\nc\n') + assert istream.readlines() == [b'a\n', b'b\n', b'c\n'] + + istream = StringStream(b'\na\nb\nc') + assert istream.readlines() == [b'\n', b'a\n', b'b\n', b'c'] + + istream = StringStream(b'\n\n\n') + assert istream.readlines() == [b'\n', b'\n', b'\n'] + + +def test_istream_iter(): + istream = StringStream(b'a') + assert tuple(istream) == (b'a',) + assert tuple(istream) == () + + istream = StringStream(b'a\nb\nc\n') + assert tuple(istream) == (b'a\n', b'b\n', b'c\n') + + istream = StringStream(b'\na\nb\nc') + assert tuple(istream) == (b'\n', b'a\n', b'b\n', b'c') + + istream = StringStream(b'\n\n\n') + assert tuple(istream) == (b'\n', b'\n', b'\n') diff --git a/tests/linmath/test_lmatrix4.py b/tests/linmath/test_lmatrix4.py index 23c1bcc5e9..05c487f807 100644 --- a/tests/linmath/test_lmatrix4.py +++ b/tests/linmath/test_lmatrix4.py @@ -87,3 +87,39 @@ def test_mat4_invert_correct(type): assert (mat * inv).is_identity() assert (inv * mat).is_identity() + + +@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f)) +def test_mat4_rows(type): + mat = type((1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16)) + + assert mat.rows[0] == (1, 2, 3, 4) + assert mat.rows[1] == (5, 6, 7, 8) + assert mat.rows[2] == (9, 10, 11, 12) + assert mat.rows[3] == (13, 14, 15, 16) + + assert mat.get_row3(0) == (1, 2, 3) + assert mat.get_row3(1) == (5, 6, 7) + assert mat.get_row3(2) == (9, 10, 11) + assert mat.get_row3(3) == (13, 14, 15) + + +@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f)) +def test_mat4_cols(type): + mat = type((1, 5, 9, 13, + 2, 6, 10, 14, + 3, 7, 11, 15, + 4, 8, 12, 16)) + + assert mat.cols[0] == (1, 2, 3, 4) + assert mat.cols[1] == (5, 6, 7, 8) + assert mat.cols[2] == (9, 10, 11, 12) + assert mat.cols[3] == (13, 14, 15, 16) + + assert mat.get_col3(0) == (1, 2, 3) + assert mat.get_col3(1) == (5, 6, 7) + assert mat.get_col3(2) == (9, 10, 11) + assert mat.get_col3(3) == (13, 14, 15) diff --git a/tests/movies/test_user_audio.py b/tests/movies/test_user_audio.py new file mode 100644 index 0000000000..13addd05f0 --- /dev/null +++ b/tests/movies/test_user_audio.py @@ -0,0 +1,15 @@ +import pytest + +from panda3d.core import UserDataAudio + + +@pytest.mark.parametrize("remove_after_read", [True, False]) +def test_userdata_audio(remove_after_read): + audio = UserDataAudio(48000, 2, remove_after_read) + audio.append(b'abcdefgh') + audio.done() + cursor = audio.open() + assert cursor.read_samples(0) == b'' + assert cursor.read_samples(1) == b'abcd' + assert cursor.read_samples(1) == b'efgh' + assert cursor.read_samples(1) == b'' diff --git a/tests/ode/conftest.py b/tests/ode/conftest.py new file mode 100644 index 0000000000..289304cf0f --- /dev/null +++ b/tests/ode/conftest.py @@ -0,0 +1,7 @@ +import pytest + + +@pytest.fixture +def world(): + ode = pytest.importorskip("panda3d.ode") + return ode.OdeWorld() diff --git a/tests/ode/test_ode_joints.py b/tests/ode/test_ode_joints.py new file mode 100644 index 0000000000..d1e202412d --- /dev/null +++ b/tests/ode/test_ode_joints.py @@ -0,0 +1,43 @@ +import pytest + + +def test_odejoint_attach_both(world): + from panda3d import ode + + body1 = ode.OdeBody(world) + body2 = ode.OdeBody(world) + + assert len(body1.joints) == 0 + assert len(body2.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(body1, body2) + + assert tuple(body1.joints) == (joint,) + assert tuple(body2.joints) == (joint,) + + +def test_odejoint_attach_0(world): + from panda3d import ode + + body = ode.OdeBody(world) + + assert len(body.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(body, None) + + assert tuple(body.joints) == (joint,) + + +def test_odejoint_attach_1(world): + from panda3d import ode + + body = ode.OdeBody(world) + + assert len(body.joints) == 0 + + joint = ode.OdeBallJoint(world) + joint.attach(None, body) + + assert tuple(body.joints) == (joint,) diff --git a/tests/pipeline/test_condition_var.py b/tests/pipeline/test_condition_var.py index 6aa8bcf320..4cfc6c226a 100644 --- a/tests/pipeline/test_condition_var.py +++ b/tests/pipeline/test_condition_var.py @@ -26,13 +26,12 @@ def test_cvar_notify_locked(): m = Mutex() cv = ConditionVarFull(m) - m.acquire() - cv.notify() - m.release() + with m: + cv.notify() + + with m: + cv.notify_all() - m.acquire() - cv.notify_all() - m.release() del cv @@ -72,16 +71,22 @@ def test_cvar_notify_thread(num_threads): break assert state['waiting'] == num_threads - m.release() # OK, now signal it, and yield. One thread must be unblocked per notify. for i in range(num_threads): cv.notify() - yield_thread() - m.acquire() - assert state['waiting'] == num_threads - i - 1 - m.release() + expected_waiters = num_threads - i - 1 + for j in range(1000): + m.release() + yield_thread() + m.acquire() + if state['waiting'] == expected_waiters: + break + + assert state['waiting'] == expected_waiters + + m.release() for thread in threads: thread.join() cv = None @@ -123,12 +128,16 @@ def test_cvar_notify_all_threads(num_threads): break assert state['waiting'] == num_threads - m.release() # OK, now signal it, and yield. All threads must unblock. cv.notify_all() - yield_thread() - m.acquire() + for i in range(1000): + m.release() + yield_thread() + m.acquire() + if state['waiting'] == 0: + break + assert state['waiting'] == 0 m.release() diff --git a/tests/pipeline/test_mutex.py b/tests/pipeline/test_mutex.py index 668d275b08..15fa435da7 100644 --- a/tests/pipeline/test_mutex.py +++ b/tests/pipeline/test_mutex.py @@ -2,6 +2,7 @@ from panda3d.core import Mutex, ReMutex from panda3d import core from random import random import pytest +import sys def test_mutex_acquire_release(): @@ -34,6 +35,19 @@ def test_mutex_try_acquire(): m.release() +def test_mutex_with(): + m = Mutex() + + rc = sys.getrefcount(m) + with m: + assert m.debug_is_locked() + + with m: + assert m.debug_is_locked() + + assert rc == sys.getrefcount(m) + + @pytest.mark.skipif(not core.Thread.is_threading_supported(), reason="Threading support disabled") def test_mutex_contention(): @@ -124,3 +138,15 @@ def test_remutex_try_acquire(): m.release() m.release() + +def test_remutex_with(): + m = ReMutex() + + rc = sys.getrefcount(m) + with m: + assert m.debug_is_locked() + with m: + assert m.debug_is_locked() + assert m.debug_is_locked() + + assert rc == sys.getrefcount(m) diff --git a/tests/prc/test_encrypt_stream.py b/tests/prc/test_encrypt_stream.py new file mode 100644 index 0000000000..d87f31a72a --- /dev/null +++ b/tests/prc/test_encrypt_stream.py @@ -0,0 +1,19 @@ +from panda3d import core + +import pytest + + +@pytest.mark.skipif(not hasattr(core, 'IDecryptStream'), reason="Requires OpenSSL") +def test_decrypt_stream(): + encrypted = b'[\x00\x10\x00d\x00\x07K\x08\x03\xabS\x13L\xab\x93\x1b\x15\xe4\xeel\x80u o\xd0\x80aY_]\x10\x8a\xb5\xff\x9d1\xc9\xd3\xac\x95\x04\xd8\xdf\x10\xa1' + decrypted = b'abcdefghijklmnopqrstuvwxyz' + + ss = core.StringStream(encrypted) + ds = core.IDecryptStream(ss, False, '0123456789') + + assert ds.read(len(decrypted)) == decrypted + assert ds.readall() == b'' + + # Allow seeking back to the beginning + ds.seekg(0) + assert ds.readall() == decrypted diff --git a/tests/putil/test_sparsearray.py b/tests/putil/test_sparsearray.py new file mode 100644 index 0000000000..b7767d6e8a --- /dev/null +++ b/tests/putil/test_sparsearray.py @@ -0,0 +1,234 @@ +from panda3d import core + + +def test_sparse_array_set_bit_to(): + """Tests SparseArray behavior for set_bit_to().""" + + s = core.SparseArray() + s.set_bit_to(5, True) + assert s.get_bit(5) + + s = core.SparseArray.all_on() + s.set_bit_to(5, False) + assert not s.get_bit(5) + + +def test_sparse_array_clear(): + """Tests SparseArray behavior for clear().""" + + s = core.SparseArray.all_on() + s.clear() + assert s.is_zero() + assert not s.is_inverse() + assert s.get_num_subranges() == 0 + assert s.get_num_on_bits() == 0 + assert s.get_num_bits() == 0 + + s = core.SparseArray() + s.set_range(5, 10) + s.clear() + assert s.is_zero() + assert not s.is_inverse() + assert s.get_num_subranges() == 0 + assert s.get_num_on_bits() == 0 + assert s.get_num_bits() == 0 + + +def test_sparse_array_clear_range(): + # Not using parametrize because there are too many values for that. + for mask in range(0x7f): + for begin in range(8): + for size in range(8): + b = core.BitArray(mask) + s = core.SparseArray(b) + + s.clear_range(begin, size) + b.clear_range(begin, size) + + assert core.BitArray(s) == b + assert s == core.SparseArray(b) + + +def test_sparse_array_set_clear_ranges(): + """Tests SparseArray behavior for setting and clearing ranges.""" + + # test clear_range with single overlapping on-range + # (clear_range extends beyond highest on-bit) + s = core.SparseArray() + s.set_range(2, 3) + s.clear_range(3, 3) + assert s.get_bit(2) + assert not s.get_bit(3) + + # same as above, using set_range_to + s = core.SparseArray() + s.set_range_to(True, 2, 3) + s.set_range_to(False, 3, 3) + assert s.get_bit(2) + assert not s.get_bit(3) + + # test clear_range using off-range which overlaps two on-ranges + # (lowest off-bit in lowest on-range, highest off-bit in highest on-range) + s = core.SparseArray() + s.set_range(2, 3) + s.set_range(7, 3) + s.clear_range(3, 6) + assert s.get_bit(2) + assert not s.get_bit(3) + assert not s.get_bit(8) + assert s.get_bit(9) + + # same as above, using set_range_to + s = core.SparseArray() + s.set_range_to(True, 2, 3) + s.set_range_to(True, 7, 3) + s.set_range_to(False, 3, 6) + assert s.get_bit(2) + assert not s.get_bit(3) + assert not s.get_bit(8) + assert s.get_bit(9) + + +def test_sparse_array_set_range(): + """Tests SparseArray behavior for set_range().""" + + # test set_range with single overlapping off-range + # (set_range extends beyond highest off-bit) + s = core.SparseArray.all_on() + s.clear_range(2, 3) + s.set_range(3, 3) + assert not s.get_bit(2) + assert s.get_bit(3) + + # same as above, using set_range_to + s = core.SparseArray.all_on() + s.set_range_to(False, 2, 3) + s.set_range_to(True, 3, 3) + assert not s.get_bit(2) + assert s.get_bit(3) + + # test set_range using on-range which overlaps two off-ranges + # (lowest on-bit in lowest off-range, highest on-bit in highest off-range) + s = core.SparseArray.all_on() + s.clear_range(2, 3) + s.clear_range(7, 3) + s.set_range(3, 6) + assert not s.get_bit(2) + assert s.get_bit(3) + assert s.get_bit(8) + assert not s.get_bit(9) + + # same as above, using set_range_to + s = core.SparseArray.all_on() + s.set_range_to(False, 2, 3) + s.set_range_to(False, 7, 3) + s.set_range_to(True, 3, 6) + assert not s.get_bit(2) + assert s.get_bit(3) + assert s.get_bit(8) + assert not s.get_bit(9) + + +def test_sparse_array_bits_in_common(): + """Tests SparseArray behavior for has_bits_in_common().""" + + s = core.SparseArray() + t = core.SparseArray() + s.set_range(2, 4) + t.set_range(5, 4) + assert s.has_bits_in_common(t) + + s = core.SparseArray() + t = core.SparseArray() + s.set_range(2, 4) + t.set_range(6, 4) + assert not s.has_bits_in_common(t) + + +def test_sparse_array_operations(): + """Tests SparseArray behavior for various operations.""" + + # test bitshift to left + s = core.SparseArray() + s.set_bit(2) + t = s << 2 + assert t.get_bit(4) + assert not t.get_bit(2) + + # test bitshift to right + s = core.SparseArray() + s.set_bit(4) + t = s >> 2 + assert t.get_bit(2) + assert not t.get_bit(4) + + # test bitwise AND + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s & t + assert not u.get_bit(0) + assert not u.get_bit(1) + assert not u.get_bit(2) + assert u.get_bit(3) + + # test bitwise OR + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s | t + assert not u.get_bit(0) + assert u.get_bit(1) + assert u.get_bit(2) + assert u.get_bit(3) + + # test bitwise XOR + s = core.SparseArray() + t = core.SparseArray() + s.set_bit(2) + s.set_bit(3) + t.set_bit(1) + t.set_bit(3) + u = s ^ t + assert not u.get_bit(0) + assert u.get_bit(1) + assert u.get_bit(2) + assert not u.get_bit(3) + + +def test_sparse_array_augm_assignment(): + """Tests SparseArray behavior for augmented assignments.""" + + # test in-place bitshift to left + s = t = core.SparseArray() + t <<= 2 + assert s is t + + # test in-place bitshift to right + s = t = core.SparseArray() + t >>= 2 + assert s is t + + # test in-place bitwise AND + s = t = core.SparseArray() + u = core.SparseArray() + t &= u + assert s is t + + # test in-place bitwise OR + s = t = core.SparseArray() + u = core.SparseArray() + t |= u + assert s is t + + # test in-place bitwise XOR + s = t = core.SparseArray() + u = core.SparseArray() + t ^= u + assert s is t