Selaa lähdekoodia

Merge branch 'master' into webgl-port

rdb 4 vuotta sitten
vanhempi
sitoutus
39e033af76

+ 85 - 23
direct/src/fsm/FSM.py

@@ -13,6 +13,8 @@ from direct.showbase.MessengerGlobal import messenger
 from direct.showbase import PythonUtil
 from direct.showbase import PythonUtil
 from direct.directnotify import DirectNotifyGlobal
 from direct.directnotify import DirectNotifyGlobal
 from direct.stdpy.threading import RLock
 from direct.stdpy.threading import RLock
+from panda3d.core import AsyncTaskManager, AsyncFuture, PythonTask
+import types
 
 
 
 
 class FSMException(Exception):
 class FSMException(Exception):
@@ -27,6 +29,19 @@ class RequestDenied(FSMException):
     pass
     pass
 
 
 
 
+class Transition(tuple):
+    """Used for the return value of fsm.request().  Behaves like a tuple, for
+    historical reasons."""
+
+    _future = None
+
+    def __await__(self):
+        if self._future:
+            yield self._future
+
+        return tuple(self)
+
+
 class FSM(DirectObject):
 class FSM(DirectObject):
     """
     """
     A Finite State Machine.  This is intended to be the base class
     A Finite State Machine.  This is intended to be the base class
@@ -154,6 +169,9 @@ class FSM(DirectObject):
     # must be approved by some filter function.
     # must be approved by some filter function.
     defaultTransitions = None
     defaultTransitions = None
 
 
+    __doneFuture = AsyncFuture()
+    __doneFuture.set_result(None)
+
     # An enum class for special states like the DEFAULT or ANY state,
     # An enum class for special states like the DEFAULT or ANY state,
     # that should be treatened by the FSM in a special way
     # that should be treatened by the FSM in a special way
     class EnumStates():
     class EnumStates():
@@ -247,7 +265,13 @@ class FSM(DirectObject):
     def forceTransition(self, request, *args):
     def forceTransition(self, request, *args):
         """Changes unconditionally to the indicated state.  This
         """Changes unconditionally to the indicated state.  This
         bypasses the filterState() function, and just calls
         bypasses the filterState() function, and just calls
-        exitState() followed by enterState()."""
+        exitState() followed by enterState().
+
+        If the FSM is currently undergoing a transition, this will
+        queue up the new transition.
+
+        Returns a future, which can be used to await the transition.
+        """
 
 
         self.fsmLock.acquire()
         self.fsmLock.acquire()
         try:
         try:
@@ -257,11 +281,13 @@ class FSM(DirectObject):
 
 
             if not self.state:
             if not self.state:
                 # Queue up the request.
                 # Queue up the request.
-                self.__requestQueue.append(PythonUtil.Functor(
-                    self.forceTransition, request, *args))
-                return
+                fut = AsyncFuture()
+                self.__requestQueue.append((PythonUtil.Functor(
+                    self.forceTransition, request, *args), fut))
+                return fut
 
 
-            self.__setState(request, *args)
+            result = self.__setState(request, *args)
+            return result._future or self.__doneFuture
         finally:
         finally:
             self.fsmLock.release()
             self.fsmLock.release()
 
 
@@ -275,6 +301,10 @@ class FSM(DirectObject):
         request is queued up and will be executed when the current
         request is queued up and will be executed when the current
         transition finishes.  Multiple requests will queue up in
         transition finishes.  Multiple requests will queue up in
         sequence.
         sequence.
+
+        The return value of this function can be used in an `await`
+        expression to suspend the current coroutine until the
+        transition is done.
         """
         """
 
 
         self.fsmLock.acquire()
         self.fsmLock.acquire()
@@ -284,12 +314,15 @@ class FSM(DirectObject):
                 self._name, request, str(args)[1:]))
                 self._name, request, str(args)[1:]))
             if not self.state:
             if not self.state:
                 # Queue up the request.
                 # Queue up the request.
-                self.__requestQueue.append(PythonUtil.Functor(
-                    self.demand, request, *args))
-                return
+                fut = AsyncFuture()
+                self.__requestQueue.append((PythonUtil.Functor(
+                    self.demand, request, *args), fut))
+                return fut
 
 
-            if not self.request(request, *args):
+            result = self.request(request, *args)
+            if not result:
                 raise RequestDenied("%s (from state: %s)" % (request, self.state))
                 raise RequestDenied("%s (from state: %s)" % (request, self.state))
+            return result._future or self.__doneFuture
         finally:
         finally:
             self.fsmLock.release()
             self.fsmLock.release()
 
 
@@ -314,7 +347,12 @@ class FSM(DirectObject):
         executing an enterState or exitState function), an
         executing an enterState or exitState function), an
         `AlreadyInTransition` exception is raised (but see `demand()`,
         `AlreadyInTransition` exception is raised (but see `demand()`,
         which will queue these requests up and apply when the
         which will queue these requests up and apply when the
-        transition is complete)."""
+        transition is complete).
+
+        If the previous state's exitFunc or the new state's enterFunc
+        is a coroutine, the state change may not have been applied by
+        the time request() returns, but you can use `await` on the
+        return value to await the transition."""
 
 
         self.fsmLock.acquire()
         self.fsmLock.acquire()
         try:
         try:
@@ -331,7 +369,7 @@ class FSM(DirectObject):
                     result = (result,) + args
                     result = (result,) + args
 
 
                 # Otherwise, assume it's a (name, *args) tuple
                 # Otherwise, assume it's a (name, *args) tuple
-                self.__setState(*result)
+                return self.__setState(*result)
 
 
             return result
             return result
         finally:
         finally:
@@ -441,11 +479,11 @@ class FSM(DirectObject):
         try:
         try:
             if self.stateArray:
             if self.stateArray:
                 if not self.state in self.stateArray:
                 if not self.state in self.stateArray:
-                    self.request(self.stateArray[0])
+                    return self.request(self.stateArray[0])
                 else:
                 else:
                     cur_index = self.stateArray.index(self.state)
                     cur_index = self.stateArray.index(self.state)
                     new_index = (cur_index + 1) % len(self.stateArray)
                     new_index = (cur_index + 1) % len(self.stateArray)
-                    self.request(self.stateArray[new_index], args)
+                    return self.request(self.stateArray[new_index], args)
             else:
             else:
                 assert self.notifier.debug(
                 assert self.notifier.debug(
                                     "stateArray empty. Can't switch to next.")
                                     "stateArray empty. Can't switch to next.")
@@ -459,11 +497,11 @@ class FSM(DirectObject):
         try:
         try:
             if self.stateArray:
             if self.stateArray:
                 if not self.state in self.stateArray:
                 if not self.state in self.stateArray:
-                    self.request(self.stateArray[0])
+                    return self.request(self.stateArray[0])
                 else:
                 else:
                     cur_index = self.stateArray.index(self.state)
                     cur_index = self.stateArray.index(self.state)
                     new_index = (cur_index - 1) % len(self.stateArray)
                     new_index = (cur_index - 1) % len(self.stateArray)
-                    self.request(self.stateArray[new_index], args)
+                    return self.request(self.stateArray[new_index], args)
             else:
             else:
                 assert self.notifier.debug(
                 assert self.notifier.debug(
                                     "stateArray empty. Can't switch to next.")
                                     "stateArray empty. Can't switch to next.")
@@ -471,8 +509,26 @@ class FSM(DirectObject):
             self.fsmLock.release()
             self.fsmLock.release()
 
 
     def __setState(self, newState, *args):
     def __setState(self, newState, *args):
-        # Internal function to change unconditionally to the indicated
-        # state.
+        # Internal function to change unconditionally to the indicated state.
+
+        transition = Transition((newState,) + args)
+
+        # See if we can transition immediately by polling the coroutine.
+        coro = self.__transition(newState, *args)
+        try:
+            coro.send(None)
+        except StopIteration:
+            # We managed to apply this straight away.
+            return transition
+
+        # Continue the state transition in a task.
+        task = PythonTask(coro)
+        mgr = AsyncTaskManager.get_global_ptr()
+        mgr.add(task)
+        transition._future = task
+        return transition
+
+    async def __transition(self, newState, *args):
         assert self.state
         assert self.state
         assert self.notify.debug("%s to state %s." % (self._name, newState))
         assert self.notify.debug("%s to state %s." % (self._name, newState))
 
 
@@ -482,8 +538,13 @@ class FSM(DirectObject):
 
 
         try:
         try:
             if not self.__callFromToFunc(self.oldState, self.newState, *args):
             if not self.__callFromToFunc(self.oldState, self.newState, *args):
-                self.__callExitFunc(self.oldState)
-                self.__callEnterFunc(self.newState, *args)
+                result = self.__callExitFunc(self.oldState)
+                if isinstance(result, types.CoroutineType):
+                    await result
+
+                result = self.__callEnterFunc(self.newState, *args)
+                if isinstance(result, types.CoroutineType):
+                    await result
         except:
         except:
             # If we got an exception during the enter or exit methods,
             # If we got an exception during the enter or exit methods,
             # go directly to state "InternalError" and raise up the
             # go directly to state "InternalError" and raise up the
@@ -503,9 +564,10 @@ class FSM(DirectObject):
         del self.newState
         del self.newState
 
 
         if self.__requestQueue:
         if self.__requestQueue:
-            request = self.__requestQueue.pop(0)
+            request, fut = self.__requestQueue.pop(0)
             assert self.notify.debug("%s continued queued request." % (self._name))
             assert self.notify.debug("%s continued queued request." % (self._name))
-            request()
+            await request()
+            fut.set_result(None)
 
 
     def __callEnterFunc(self, name, *args):
     def __callEnterFunc(self, name, *args):
         # Calls the appropriate enter function when transitioning into
         # Calls the appropriate enter function when transitioning into
@@ -517,7 +579,7 @@ class FSM(DirectObject):
             # If there's no matching enterFoo() function, call
             # If there's no matching enterFoo() function, call
             # defaultEnter() instead.
             # defaultEnter() instead.
             func = self.defaultEnter
             func = self.defaultEnter
-        func(*args)
+        return func(*args)
 
 
     def __callFromToFunc(self, oldState, newState, *args):
     def __callFromToFunc(self, oldState, newState, *args):
         # Calls the appropriate fromTo function when transitioning into
         # Calls the appropriate fromTo function when transitioning into
@@ -540,7 +602,7 @@ class FSM(DirectObject):
             # If there's no matching exitFoo() function, call
             # If there's no matching exitFoo() function, call
             # defaultExit() instead.
             # defaultExit() instead.
             func = self.defaultExit
             func = self.defaultExit
-        func()
+        return func()
 
 
     def __repr__(self):
     def __repr__(self):
         return self.__str__()
         return self.__str__()

+ 6 - 1
direct/src/interval/CMakeLists.txt

@@ -37,11 +37,16 @@ set(P3INTERVAL_SOURCES
   waitInterval.cxx
   waitInterval.cxx
 )
 )
 
 
+set(P3INTERVAL_IGATEEXT
+  cInterval_ext.cxx
+  cInterval_ext.h
+)
+
 composite_sources(p3interval P3INTERVAL_SOURCES)
 composite_sources(p3interval P3INTERVAL_SOURCES)
 add_component_library(p3interval SYMBOL BUILDING_DIRECT_INTERVAL
 add_component_library(p3interval SYMBOL BUILDING_DIRECT_INTERVAL
   ${P3INTERVAL_HEADERS} ${P3INTERVAL_SOURCES})
   ${P3INTERVAL_HEADERS} ${P3INTERVAL_SOURCES})
 target_link_libraries(p3interval p3directbase panda)
 target_link_libraries(p3interval p3directbase panda)
-target_interrogate(p3interval ALL)
+target_interrogate(p3interval ALL EXTENSIONS ${P3INTERVAL_IGATEEXT})
 
 
 if(NOT BUILD_METALIBS)
 if(NOT BUILD_METALIBS)
   install(TARGETS p3interval
   install(TARGETS p3interval

+ 3 - 0
direct/src/interval/cInterval.h

@@ -19,6 +19,7 @@
 #include "pvector.h"
 #include "pvector.h"
 #include "config_interval.h"
 #include "config_interval.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
+#include "extension.h"
 
 
 class CIntervalManager;
 class CIntervalManager;
 
 
@@ -120,6 +121,8 @@ PUBLISHED:
   bool step_play();
   bool step_play();
 
 
 PUBLISHED:
 PUBLISHED:
+  EXTENSION(PyObject *__await__(PyObject *self));
+
   MAKE_PROPERTY(name, get_name);
   MAKE_PROPERTY(name, get_name);
   MAKE_PROPERTY(duration, get_duration);
   MAKE_PROPERTY(duration, get_duration);
   MAKE_PROPERTY(open_ended, get_open_ended);
   MAKE_PROPERTY(open_ended, get_open_ended);

+ 61 - 0
direct/src/interval/cInterval_ext.cxx

@@ -0,0 +1,61 @@
+/**
+ * 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 cInterval_ext.cxx
+ * @author rdb
+ * @date 2020-10-17
+ */
+
+#include "cInterval_ext.h"
+#include "cIntervalManager.h"
+#include "asyncFuture.h"
+
+#ifdef HAVE_PYTHON
+
+#ifndef CPPPARSER
+extern struct Dtool_PyTypedObject Dtool_CInterval;
+#endif
+
+/**
+ * Yields continuously until the interval is done.
+ */
+static PyObject *gen_next(PyObject *self) {
+  const CInterval *ival;
+  if (!Dtool_Call_ExtractThisPointer(self, Dtool_CInterval, (void **)&ival)) {
+    return nullptr;
+  }
+
+  if (ival->get_state() != CInterval::S_final) {
+    // Try again next frame.
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+  else {
+    PyErr_SetNone(PyExc_StopIteration);
+    return nullptr;
+  }
+}
+
+/**
+ * Awaiting an interval starts it and yields a future until it is done.
+ */
+PyObject *Extension<CInterval>::
+__await__(PyObject *self) {
+  if (_this->get_state() != CInterval::S_initial) {
+    PyErr_SetString(PyExc_RuntimeError, "Can only await an interval that is in the initial state.");
+    return nullptr;
+  }
+
+  // This may be overridden from Python (such as is the case for Sequence), so
+  // we call this via Python.
+  PyObject *result = PyObject_CallMethod(self, "start", nullptr);
+  Py_XDECREF(result);
+  return Dtool_NewGenerator(self, &gen_next);
+}
+
+#endif  // HAVE_PYTHON

+ 37 - 0
direct/src/interval/cInterval_ext.h

@@ -0,0 +1,37 @@
+/**
+ * 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 cInterval_ext.h
+ * @author rdb
+ * @date 2020-10-17
+ */
+
+#ifndef CINTERVAL_EXT_H
+#define CINTERVAL_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "cInterval.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for CInterval, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<CInterval> : public ExtensionBase<CInterval> {
+public:
+  PyObject *__await__(PyObject *self);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // CINTERVAL_EXT_H

+ 7 - 40
direct/src/showbase/Loader.py

@@ -31,42 +31,6 @@ class Loader(DirectObject):
         # This indicates that this class behaves like a Future.
         # This indicates that this class behaves like a Future.
         _asyncio_future_blocking = False
         _asyncio_future_blocking = False
 
 
-        class _ResultAwaiter(object):
-            """Reinvents generators because of PEP 479, sigh.  See #513."""
-
-            __slots__ = 'requestList', 'index'
-
-            def __init__(self, requestList):
-                self.requestList = requestList
-                self.index = 0
-
-            def __await__(self):
-                return self
-
-            def __anext__(self):
-                if self.index >= len(self.requestList):
-                    raise StopAsyncIteration
-                return self
-
-            def __iter__(self):
-                return self
-
-            def __next__(self):
-                i = self.index
-                request = self.requestList[i]
-                if not request.done():
-                    return request
-
-                self.index = i + 1
-
-                result = request.result()
-                if isinstance(result, PandaNode):
-                    result = NodePath(result)
-
-                exc = StopIteration(result)
-                exc.value = result
-                raise exc
-
         def __init__(self, loader, numObjects, gotList, callback, extraArgs):
         def __init__(self, loader, numObjects, gotList, callback, extraArgs):
             self._loader = loader
             self._loader = loader
             self.objects = [None] * numObjects
             self.objects = [None] * numObjects
@@ -124,13 +88,15 @@ class Loader(DirectObject):
 
 
             if self.requests:
             if self.requests:
                 self._asyncio_future_blocking = True
                 self._asyncio_future_blocking = True
+                while self.requests:
+                    yield self
 
 
             if self.gotList:
             if self.gotList:
-                return self._ResultAwaiter([self])
+                return self.objects
             else:
             else:
-                return self._ResultAwaiter(self.requestList)
+                return self.objects[0]
 
 
-        def __aiter__(self):
+        async def __aiter__(self):
             """ This allows using `async for` to iterate asynchronously over
             """ This allows using `async for` to iterate asynchronously over
             the results of this class.  It does guarantee to return the
             the results of this class.  It does guarantee to return the
             results in order, though, even though they may not be loaded in
             results in order, though, even though they may not be loaded in
@@ -138,7 +104,8 @@ class Loader(DirectObject):
             requestList = self.requestList
             requestList = self.requestList
             assert requestList is not None, "Request was cancelled."
             assert requestList is not None, "Request was cancelled."
 
 
-            return self._ResultAwaiter(requestList)
+            for req in requestList:
+                yield await req
 
 
     # special methods
     # special methods
     def __init__(self, base):
     def __init__(self, base):

+ 3 - 3
dtool/src/prc/streamWrapper.h

@@ -65,7 +65,7 @@ PUBLISHED:
   ~IStreamWrapper();
   ~IStreamWrapper();
 
 
   INLINE std::istream *get_istream() const;
   INLINE std::istream *get_istream() const;
-  MAKE_PROPERTY(std::istream, get_istream);
+  MAKE_PROPERTY(istream, get_istream);
 
 
 public:
 public:
   void read(char *buffer, std::streamsize num_bytes);
   void read(char *buffer, std::streamsize num_bytes);
@@ -92,7 +92,7 @@ PUBLISHED:
   ~OStreamWrapper();
   ~OStreamWrapper();
 
 
   INLINE std::ostream *get_ostream() const;
   INLINE std::ostream *get_ostream() const;
-  MAKE_PROPERTY(std::ostream, get_ostream);
+  MAKE_PROPERTY(ostream, get_ostream);
 
 
 public:
 public:
   void write(const char *buffer, std::streamsize num_bytes);
   void write(const char *buffer, std::streamsize num_bytes);
@@ -128,7 +128,7 @@ PUBLISHED:
   ~StreamWrapper();
   ~StreamWrapper();
 
 
   INLINE std::iostream *get_iostream() const;
   INLINE std::iostream *get_iostream() const;
-  MAKE_PROPERTY(std::iostream, get_iostream);
+  MAKE_PROPERTY(iostream, get_iostream);
 
 
 private:
 private:
   std::iostream *_iostream;
   std::iostream *_iostream;

+ 6 - 2
makepanda/makepanda.py

@@ -1460,8 +1460,9 @@ def CompileCxx(obj,src,opts):
 
 
 def CompileBison(wobj, wsrc, opts):
 def CompileBison(wobj, wsrc, opts):
     ifile = os.path.basename(wsrc)
     ifile = os.path.basename(wsrc)
-    wdsth = GetOutputDir()+"/include/" + ifile[:-4] + ".h"
-    wdstc = GetOutputDir()+"/tmp/" + ifile + ".cxx"
+    wdsth = GetOutputDir() + "/include/" + ifile[:-4] + ".h"
+    wdsth2 = GetOutputDir() + "/tmp/" + ifile + ".h"
+    wdstc = GetOutputDir() + "/tmp/" + ifile + ".cxx"
     pre = GetValueOption(opts, "BISONPREFIX_")
     pre = GetValueOption(opts, "BISONPREFIX_")
     bison = GetBison()
     bison = GetBison()
     if bison is None:
     if bison is None:
@@ -1471,6 +1472,7 @@ def CompileBison(wobj, wsrc, opts):
            os.path.isfile(base + '.cxx.prebuilt'):
            os.path.isfile(base + '.cxx.prebuilt'):
             CopyFile(wdstc, base + '.cxx.prebuilt')
             CopyFile(wdstc, base + '.cxx.prebuilt')
             CopyFile(wdsth, base + '.h.prebuilt')
             CopyFile(wdsth, base + '.h.prebuilt')
+            CopyFile(wdsth2, base + '.h.prebuilt')
         else:
         else:
             exit('Could not find bison!')
             exit('Could not find bison!')
     else:
     else:
@@ -5087,6 +5089,7 @@ if not PkgSkip("DIRECT"):
     IGATEFILES=GetDirectoryContents('direct/src/interval', ["*.h", "*_composite*.cxx"])
     IGATEFILES=GetDirectoryContents('direct/src/interval', ["*.h", "*_composite*.cxx"])
     TargetAdd('libp3interval.in', opts=OPTS, input=IGATEFILES)
     TargetAdd('libp3interval.in', opts=OPTS, input=IGATEFILES)
     TargetAdd('libp3interval.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3interval', 'SRCDIR:direct/src/interval'])
     TargetAdd('libp3interval.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3interval', 'SRCDIR:direct/src/interval'])
+    PyTargetAdd('p3interval_cInterval_ext.obj', opts=OPTS, input='cInterval_ext.cxx')
 
 
 #
 #
 # DIRECTORY: direct/src/showbase/
 # DIRECTORY: direct/src/showbase/
@@ -5152,6 +5155,7 @@ if not PkgSkip("DIRECT"):
     PyTargetAdd('direct.pyd', input='libp3showbase_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3showbase_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3deadrec_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3deadrec_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3interval_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3interval_igate.obj')
+    PyTargetAdd('direct.pyd', input='p3interval_cInterval_ext.obj')
     if GetTarget() != 'emscripten':
     if GetTarget() != 'emscripten':
         PyTargetAdd('direct.pyd', input='libp3distributed_igate.obj')
         PyTargetAdd('direct.pyd', input='libp3distributed_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3motiontrail_igate.obj')
     PyTargetAdd('direct.pyd', input='libp3motiontrail_igate.obj')

+ 1 - 3
panda/src/collide/collisionHandlerGravity.I

@@ -52,8 +52,6 @@ get_reach() const {
  *
  *
  * The object might not necessarily be at rest.  Use is_on_ground() if you
  * The object might not necessarily be at rest.  Use is_on_ground() if you
  * want to know whether the object is on the ground and at rest.
  * want to know whether the object is on the ground and at rest.
- *
- * See Also: is_in_outer_space()
  */
  */
 INLINE PN_stdfloat CollisionHandlerGravity::
 INLINE PN_stdfloat CollisionHandlerGravity::
 get_airborne_height() const {
 get_airborne_height() const {
@@ -73,7 +71,7 @@ is_on_ground() const {
 
 
 /**
 /**
  * How hard did the object hit the ground.  This value is set on impact with
  * How hard did the object hit the ground.  This value is set on impact with
- * the ground.  You may want to watch (poll) on is_on_groun() and when that is
+ * the ground.  You may want to watch (poll) on is_on_ground() and when that is
  * true, call get_impact_velocity(). Normally I avoid polling, but we are
  * true, call get_impact_velocity(). Normally I avoid polling, but we are
  * calling is_on_ground() frequently anyway.
  * calling is_on_ground() frequently anyway.
  */
  */

+ 4 - 3
panda/src/downloader/httpAuthorization.cxx

@@ -14,6 +14,7 @@
 #include "httpAuthorization.h"
 #include "httpAuthorization.h"
 #include "httpChannel.h"
 #include "httpChannel.h"
 #include "urlSpec.h"
 #include "urlSpec.h"
+#include "string_utils.h"
 
 
 #ifdef HAVE_OPENSSL
 #ifdef HAVE_OPENSSL
 
 
@@ -126,7 +127,7 @@ parse_authentication_schemes(HTTPAuthorization::AuthenticationSchemes &schemes,
       ++q;
       ++q;
     }
     }
     // Here's our first scheme.
     // Here's our first scheme.
-    string scheme = HTTPChannel::downcase(field_value.substr(p, q - p));
+    string scheme = downcase(field_value.substr(p, q - p));
     Tokens *tokens = &(schemes[scheme]);
     Tokens *tokens = &(schemes[scheme]);
 
 
     // Now pull off the tokens, one at a time.
     // Now pull off the tokens, one at a time.
@@ -139,7 +140,7 @@ parse_authentication_schemes(HTTPAuthorization::AuthenticationSchemes &schemes,
       }
       }
       if (field_value[q] == '=') {
       if (field_value[q] == '=') {
         // This is a token.
         // This is a token.
-        string token = HTTPChannel::downcase(field_value.substr(p, q - p));
+        string token = downcase(field_value.substr(p, q - p));
         string value;
         string value;
         p = scan_quoted_or_unquoted_string(value, field_value, q + 1);
         p = scan_quoted_or_unquoted_string(value, field_value, q + 1);
         (*tokens)[token] = value;
         (*tokens)[token] = value;
@@ -152,7 +153,7 @@ parse_authentication_schemes(HTTPAuthorization::AuthenticationSchemes &schemes,
 
 
       } else {
       } else {
         // This is not a token; it must be the start of a new scheme.
         // This is not a token; it must be the start of a new scheme.
-        scheme = HTTPChannel::downcase(field_value.substr(p, q - p));
+        scheme = downcase(field_value.substr(p, q - p));
         tokens = &(schemes[scheme]);
         tokens = &(schemes[scheme]);
         p = q + 1;
         p = q + 1;
       }
       }

+ 57 - 25
panda/src/downloader/httpChannel.cxx

@@ -22,6 +22,11 @@
 #include "virtualFileMountHTTP.h"
 #include "virtualFileMountHTTP.h"
 #include "ramfile.h"
 #include "ramfile.h"
 #include "globPattern.h"
 #include "globPattern.h"
+#include "string_utils.h"
+
+#ifdef HAVE_ZLIB
+#include "zStream.h"
+#endif
 
 
 #include <stdio.h>
 #include <stdio.h>
 
 
@@ -111,12 +116,14 @@ HTTPChannel(HTTPClient *client) :
   _done_state = S_new;
   _done_state = S_new;
   _started_download = false;
   _started_download = false;
   _sent_so_far = 0;
   _sent_so_far = 0;
+  _body_socket_stream = nullptr;
   _body_stream = nullptr;
   _body_stream = nullptr;
   _owns_body_stream = false;
   _owns_body_stream = false;
   _sbio = nullptr;
   _sbio = nullptr;
   _cipher_list = _client->get_cipher_list();
   _cipher_list = _client->get_cipher_list();
   _last_status_code = 0;
   _last_status_code = 0;
   _last_run_time = 0.0f;
   _last_run_time = 0.0f;
+  _download_dest = DD_none;
   _download_to_ramfile = nullptr;
   _download_to_ramfile = nullptr;
   _download_to_stream = nullptr;
   _download_to_stream = nullptr;
 }
 }
@@ -550,7 +557,7 @@ run() {
  * The user is responsible for passing the returned istream to
  * The user is responsible for passing the returned istream to
  * close_read_body() later.
  * close_read_body() later.
  */
  */
-ISocketStream *HTTPChannel::
+std::istream *HTTPChannel::
 open_read_body() {
 open_read_body() {
   reset_body_stream();
   reset_body_stream();
 
 
@@ -560,14 +567,14 @@ open_read_body() {
 
 
   string transfer_coding = downcase(get_header_value("Transfer-Encoding"));
   string transfer_coding = downcase(get_header_value("Transfer-Encoding"));
 
 
-  ISocketStream *result;
+  std::istream *result;
   if (transfer_coding == "chunked") {
   if (transfer_coding == "chunked") {
     // "chunked" transfer encoding.  This means we will have to decode the
     // "chunked" transfer encoding.  This means we will have to decode the
     // length of the file as we read it in chunks.  The IChunkedStream does
     // length of the file as we read it in chunks.  The IChunkedStream does
     // this.
     // this.
     _state = S_reading_body;
     _state = S_reading_body;
     _read_index++;
     _read_index++;
-    result = new IChunkedStream(_source, this);
+    _body_socket_stream = new IChunkedStream(_source, this);
 
 
   } else {
   } else {
     // If the transfer encoding is anything else, assume "identity". This is
     // If the transfer encoding is anything else, assume "identity". This is
@@ -576,11 +583,39 @@ open_read_body() {
     // file otherwise.
     // file otherwise.
     _state = S_reading_body;
     _state = S_reading_body;
     _read_index++;
     _read_index++;
-    result = new IIdentityStream(_source, this, _got_file_size, _file_size);
+    _body_socket_stream = new IIdentityStream(_source, this, _got_file_size, _file_size);
+  }
+  result = _body_socket_stream;
+
+  string content_encoding = trim(get_header_value("Content-Encoding"));
+  if (!content_encoding.empty()) {
+    vector_string content_encodings;
+    tokenize(downcase(content_encoding), content_encodings, ",");
+    for (const string &encoding : content_encodings) {
+      string trimmed = trim(encoding);
+      if (trimmed == "identity") {
+        continue;
+      }
+#ifdef HAVE_ZLIB
+      else if (trimmed == "gzip" || trimmed == "deflate" || trimmed == "x-gzip") {
+        // "deflate" actually includes zlib header, which is accepted as well
+        result = new IDecompressStream(result, true, -1, true);
+      }
+#endif
+      else {
+        downloader_cat.error()
+          << "Content-Encoding not supported: " << trimmed << "\n";
+        delete result;
+        _body_socket_stream = nullptr;
+        _body_stream = nullptr;
+        _owns_body_stream = false;
+        return nullptr;
+      }
+    }
   }
   }
 
 
-  result->_channel = this;
   _body_stream = result;
   _body_stream = result;
+  _body_socket_stream->_channel = this;
   _owns_body_stream = false;
   _owns_body_stream = false;
 
 
   return result;
   return result;
@@ -785,28 +820,14 @@ get_connection() {
   return stream;
   return stream;
 }
 }
 
 
-/**
- * Returns the input string with all uppercase letters converted to lowercase.
- */
-string HTTPChannel::
-downcase(const string &s) {
-  string result;
-  result.reserve(s.size());
-  string::const_iterator p;
-  for (p = s.begin(); p != s.end(); ++p) {
-    result += tolower(*p);
-  }
-  return result;
-}
-
 /**
 /**
  * Called by ISocketStream destructor when _body_stream is destructing.
  * Called by ISocketStream destructor when _body_stream is destructing.
  */
  */
 void HTTPChannel::
 void HTTPChannel::
 body_stream_destructs(ISocketStream *stream) {
 body_stream_destructs(ISocketStream *stream) {
-  if (stream == _body_stream) {
+  if (stream == _body_socket_stream) {
     if (_state == S_reading_body) {
     if (_state == S_reading_body) {
-      switch (_body_stream->get_read_state()) {
+      switch (_body_socket_stream->get_read_state()) {
       case ISocketStream::RS_complete:
       case ISocketStream::RS_complete:
         finished_body(false);
         finished_body(false);
         break;
         break;
@@ -820,6 +841,8 @@ body_stream_destructs(ISocketStream *stream) {
         break;
         break;
       }
       }
     }
     }
+
+    _body_socket_stream = nullptr;
     _body_stream = nullptr;
     _body_stream = nullptr;
     _owns_body_stream = false;
     _owns_body_stream = false;
   }
   }
@@ -2160,7 +2183,7 @@ run_reading_body() {
     std::getline(*_body_stream, line);
     std::getline(*_body_stream, line);
   }
   }
 
 
-  if (!_body_stream->is_closed()) {
+  if (!_body_socket_stream->is_closed()) {
     // There's more to come later.
     // There's more to come later.
     return true;
     return true;
   }
   }
@@ -2281,7 +2304,7 @@ run_download_to_file() {
 
 
   _download_to_stream->flush();
   _download_to_stream->flush();
 
 
-  if (_body_stream->is_closed()) {
+  if (_body_socket_stream->is_closed()) {
     // Done.
     // Done.
     reset_body_stream();
     reset_body_stream();
     close_download_stream();
     close_download_stream();
@@ -2331,7 +2354,7 @@ run_download_to_ram() {
     count = _body_stream->gcount();
     count = _body_stream->gcount();
   }
   }
 
 
-  if (_body_stream->is_closed()) {
+  if (_body_socket_stream->is_closed()) {
     // Done.
     // Done.
     reset_body_stream();
     reset_body_stream();
     close_download_stream();
     close_download_stream();
@@ -2392,7 +2415,7 @@ run_download_to_stream() {
 
 
   _download_to_stream->flush();
   _download_to_stream->flush();
 
 
-  if (_body_stream->is_closed()) {
+  if (_body_socket_stream->is_closed()) {
     // Done.
     // Done.
     reset_body_stream();
     reset_body_stream();
     close_download_stream();
     close_download_stream();
@@ -3635,6 +3658,14 @@ make_header() {
       << "Content-Length: " << _body.length() << "\r\n";
       << "Content-Length: " << _body.length() << "\r\n";
   }
   }
 
 
+#ifdef HAVE_ZLIB
+  stream
+    << "Accept-Encoding: gzip, deflate, identity\r\n";
+#else
+  stream
+    << "Accept-Encoding: identity\r\n";
+#endif
+
   _header = stream.str();
   _header = stream.str();
 }
 }
 
 
@@ -3810,6 +3841,7 @@ reset_body_stream() {
       nassertv(_body_stream == nullptr && !_owns_body_stream);
       nassertv(_body_stream == nullptr && !_owns_body_stream);
     }
     }
   } else {
   } else {
+    _body_socket_stream = nullptr;
     _body_stream = nullptr;
     _body_stream = nullptr;
   }
   }
 }
 }

+ 3 - 4
panda/src/downloader/httpChannel.h

@@ -182,7 +182,7 @@ PUBLISHED:
   bool run();
   bool run();
   INLINE void begin_connect_to(const DocumentSpec &url);
   INLINE void begin_connect_to(const DocumentSpec &url);
 
 
-  ISocketStream *open_read_body();
+  std::istream *open_read_body();
   void close_read_body(std::istream *stream) const;
   void close_read_body(std::istream *stream) const;
 
 
   BLOCKING bool download_to_file(const Filename &filename, bool subdocument_resumes = true);
   BLOCKING bool download_to_file(const Filename &filename, bool subdocument_resumes = true);
@@ -195,7 +195,6 @@ PUBLISHED:
   INLINE bool is_download_complete() const;
   INLINE bool is_download_complete() const;
 
 
 public:
 public:
-  static std::string downcase(const std::string &s);
   void body_stream_destructs(ISocketStream *stream);
   void body_stream_destructs(ISocketStream *stream);
 
 
 private:
 private:
@@ -336,7 +335,6 @@ private:
   DocumentSpec _document_spec;
   DocumentSpec _document_spec;
   DocumentSpec _request;
   DocumentSpec _request;
   HTTPEnum::Method _method;
   HTTPEnum::Method _method;
-  std::string request_path;
   std::string _header;
   std::string _header;
   std::string _body;
   std::string _body;
   std::string _content_type;
   std::string _content_type;
@@ -417,7 +415,8 @@ private:
   size_t _sent_so_far;
   size_t _sent_so_far;
   std::string _current_field_name;
   std::string _current_field_name;
   std::string _current_field_value;
   std::string _current_field_value;
-  ISocketStream *_body_stream;
+  ISocketStream *_body_socket_stream;
+  std::istream *_body_stream;
   bool _owns_body_stream;
   bool _owns_body_stream;
   BIO *_sbio;
   BIO *_sbio;
   std::string _cipher_list;
   std::string _cipher_list;

+ 6 - 47
panda/src/downloader/httpClient.cxx

@@ -21,6 +21,7 @@
 #include "httpBasicAuthorization.h"
 #include "httpBasicAuthorization.h"
 #include "httpDigestAuthorization.h"
 #include "httpDigestAuthorization.h"
 #include "globPattern.h"
 #include "globPattern.h"
+#include "string_utils.h"
 
 
 #ifdef HAVE_OPENSSL
 #ifdef HAVE_OPENSSL
 
 
@@ -30,48 +31,6 @@ using std::string;
 
 
 PT(HTTPClient) HTTPClient::_global_ptr;
 PT(HTTPClient) HTTPClient::_global_ptr;
 
 
-/**
- *
- */
-static string
-trim_blanks(const string &str) {
-  size_t start = 0;
-  while (start < str.length() && isspace(str[start])) {
-    start++;
-  }
-
-  size_t end = str.length();
-  while (end > start && isspace(str[end - 1])) {
-    end--;
-  }
-
-  return str.substr(start, end - start);
-}
-
-/**
- * Chops the source string up into pieces delimited by any of the characters
- * specified in delimiters.  Repeated delimiter characters represent zero-
- * length tokens.
- *
- * It is the user's responsibility to ensure the output vector is cleared
- * before calling this function; the results will simply be appended to the
- * end of the vector.
- */
-static void
-tokenize(const string &str, vector_string &words, const string &delimiters) {
-  size_t p = 0;
-  while (p < str.length()) {
-    size_t q = str.find_first_of(delimiters, p);
-    if (q == string::npos) {
-      words.push_back(str.substr(p));
-      return;
-    }
-    words.push_back(str.substr(p, q - p));
-    p = q + 1;
-  }
-  words.push_back(string());
-}
-
 #ifndef NDEBUG
 #ifndef NDEBUG
 /**
 /**
  * This method is attached as a callback for SSL messages only when debug
  * This method is attached as a callback for SSL messages only when debug
@@ -341,7 +300,7 @@ void HTTPClient::
 set_proxy_spec(const string &proxy_spec) {
 set_proxy_spec(const string &proxy_spec) {
   clear_proxy();
   clear_proxy();
 
 
-  string trim_proxy_spec = trim_blanks(proxy_spec);
+  string trim_proxy_spec = trim(proxy_spec);
 
 
   // Tokenize the string based on the semicolons.
   // Tokenize the string based on the semicolons.
   if (!trim_proxy_spec.empty()) {
   if (!trim_proxy_spec.empty()) {
@@ -359,10 +318,10 @@ set_proxy_spec(const string &proxy_spec) {
       size_t equals = spec.find('=');
       size_t equals = spec.find('=');
       if (equals == string::npos) {
       if (equals == string::npos) {
         scheme = "";
         scheme = "";
-        proxy = trim_blanks(spec);
+        proxy = trim(spec);
       } else {
       } else {
-        scheme = trim_blanks(spec.substr(0, equals));
-        proxy = trim_blanks(spec.substr(equals + 1));
+        scheme = trim(spec.substr(0, equals));
+        proxy = trim(spec.substr(equals + 1));
       }
       }
 
 
       if (proxy == "DIRECT" || proxy.empty()) {
       if (proxy == "DIRECT" || proxy.empty()) {
@@ -426,7 +385,7 @@ set_direct_host_spec(const string &direct_host_spec) {
   for (vector_string::const_iterator hi = hosts.begin();
   for (vector_string::const_iterator hi = hosts.begin();
        hi != hosts.end();
        hi != hosts.end();
        ++hi) {
        ++hi) {
-    string spec = trim_blanks(*hi);
+    string spec = trim(*hi);
 
 
     // We should be careful to avoid adding any empty hostnames to the list.
     // We should be careful to avoid adding any empty hostnames to the list.
     // In particular, we will get one empty hostname if the direct_host_spec
     // In particular, we will get one empty hostname if the direct_host_spec

+ 18 - 10
panda/src/downloader/httpCookie.I

@@ -11,15 +11,6 @@
  * @date 2004-08-26
  * @date 2004-08-26
  */
  */
 
 
-/**
- * Constructs an empty cookie.
- */
-INLINE HTTPCookie::
-HTTPCookie() :
-  _secure(false)
-{
-}
-
 /**
 /**
  * Constructs a cookie according to the indicated string, presumably the tag
  * Constructs a cookie according to the indicated string, presumably the tag
  * of a Set-Cookie header.  There is no way to detect a formatting error in
  * of a Set-Cookie header.  There is no way to detect a formatting error in
@@ -40,7 +31,8 @@ HTTPCookie(const std::string &name, const std::string &path, const std::string &
   _name(name),
   _name(name),
   _path(path),
   _path(path),
   _domain(domain),
   _domain(domain),
-  _secure(false)
+  _secure(false),
+  _samesite(SS_unspecified)
 {
 {
 }
 }
 
 
@@ -168,6 +160,22 @@ get_secure() const {
   return _secure;
   return _secure;
 }
 }
 
 
+/**
+ *
+ */
+INLINE void HTTPCookie::
+set_samesite(SameSite samesite) {
+  _samesite = samesite;
+}
+
+/**
+ *
+ */
+INLINE HTTPCookie::SameSite HTTPCookie::
+get_samesite() const {
+  return _samesite;
+}
+
 /**
 /**
  * Returns true if the cookie's expiration date is before the indicated date,
  * Returns true if the cookie's expiration date is before the indicated date,
  * false otherwise.
  * false otherwise.

+ 39 - 3
panda/src/downloader/httpCookie.cxx

@@ -16,6 +16,7 @@
 #ifdef HAVE_OPENSSL
 #ifdef HAVE_OPENSSL
 
 
 #include "httpChannel.h"
 #include "httpChannel.h"
+#include "string_utils.h"
 
 
 #include <ctype.h>
 #include <ctype.h>
 
 
@@ -59,6 +60,7 @@ update_from(const HTTPCookie &other) {
   _value = other._value;
   _value = other._value;
   _expires = other._expires;
   _expires = other._expires;
   _secure = other._secure;
   _secure = other._secure;
+  _samesite = other._samesite;
 }
 }
 
 
 /**
 /**
@@ -74,6 +76,7 @@ parse_set_cookie(const string &format, const URLSpec &url) {
   _path = url.get_path();
   _path = url.get_path();
   _expires = HTTPDate();
   _expires = HTTPDate();
   _secure = false;
   _secure = false;
+  _samesite = SS_unspecified;
 
 
   bool okflag = true;
   bool okflag = true;
   bool first_param = true;
   bool first_param = true;
@@ -147,12 +150,16 @@ output(std::ostream &out) const {
       << "; path=" << _path << "; domain=" << _domain;
       << "; path=" << _path << "; domain=" << _domain;
 
 
   if (has_expires()) {
   if (has_expires()) {
-    out << "; expires=" << _expires;
+    out << "; expires=" << _expires.get_string();
   }
   }
 
 
   if (_secure) {
   if (_secure) {
     out << "; secure";
     out << "; secure";
   }
   }
+
+  if (_samesite != SS_unspecified) {
+    out << "; samesite=" << _samesite;
+  }
 }
 }
 
 
 /**
 /**
@@ -178,7 +185,7 @@ parse_cookie_param(const string &param, bool first_param) {
     _value = value;
     _value = value;
 
 
   } else {
   } else {
-    key = HTTPChannel::downcase(key);
+    key = downcase(key);
     if (key == "expires") {
     if (key == "expires") {
       _expires = HTTPDate(value);
       _expires = HTTPDate(value);
       if (!_expires.is_valid()) {
       if (!_expires.is_valid()) {
@@ -189,7 +196,7 @@ parse_cookie_param(const string &param, bool first_param) {
       _path = value;
       _path = value;
 
 
     } else if (key == "domain") {
     } else if (key == "domain") {
-      _domain = HTTPChannel::downcase(value);
+      _domain = downcase(value);
 
 
       // From RFC 2965: If an explicitly specified value does not start with a
       // From RFC 2965: If an explicitly specified value does not start with a
       // dot, the user agent supplies a leading dot.
       // dot, the user agent supplies a leading dot.
@@ -200,6 +207,18 @@ parse_cookie_param(const string &param, bool first_param) {
     } else if (key == "secure") {
     } else if (key == "secure") {
       _secure = true;
       _secure = true;
 
 
+    } else if (key == "samesite") {
+      value = downcase(value);
+      if (value == "lax") {
+        _samesite = SS_lax;
+      }
+      else if (value == "strict") {
+        _samesite = SS_strict;
+      }
+      else if (value == "none") {
+        _samesite = SS_none;
+      }
+
     } else {
     } else {
       return false;
       return false;
     }
     }
@@ -208,4 +227,21 @@ parse_cookie_param(const string &param, bool first_param) {
   return true;
   return true;
 }
 }
 
 
+std::ostream &operator << (std::ostream &out, HTTPCookie::SameSite samesite) {
+  switch (samesite) {
+  case HTTPCookie::SS_unspecified:
+    return out;
+
+  case HTTPCookie::SS_lax:
+    return out << "lax";
+
+  case HTTPCookie::SS_strict:
+    return out << "strict";
+
+  case HTTPCookie::SS_none:
+    return out << "none";
+  }
+  return out;
+}
+
 #endif  // HAVE_OPENSSL
 #endif  // HAVE_OPENSSL

+ 22 - 2
panda/src/downloader/httpCookie.h

@@ -31,7 +31,7 @@
  */
  */
 class EXPCL_PANDA_DOWNLOADER HTTPCookie {
 class EXPCL_PANDA_DOWNLOADER HTTPCookie {
 PUBLISHED:
 PUBLISHED:
-  INLINE HTTPCookie();
+  INLINE HTTPCookie() = default;
   INLINE explicit HTTPCookie(const std::string &format, const URLSpec &url);
   INLINE explicit HTTPCookie(const std::string &format, const URLSpec &url);
   INLINE explicit HTTPCookie(const std::string &name, const std::string &path,
   INLINE explicit HTTPCookie(const std::string &name, const std::string &path,
                              const std::string &domain);
                              const std::string &domain);
@@ -57,6 +57,16 @@ PUBLISHED:
   INLINE void set_secure(bool flag);
   INLINE void set_secure(bool flag);
   INLINE bool get_secure() const;
   INLINE bool get_secure() const;
 
 
+  enum SameSite {
+    SS_unspecified,
+    SS_lax,
+    SS_strict,
+    SS_none,
+  };
+
+  INLINE void set_samesite(SameSite samesite);
+  INLINE SameSite get_samesite() const;
+
   bool operator < (const HTTPCookie &other) const;
   bool operator < (const HTTPCookie &other) const;
   void update_from(const HTTPCookie &other);
   void update_from(const HTTPCookie &other);
 
 
@@ -66,6 +76,14 @@ PUBLISHED:
 
 
   void output(std::ostream &out) const;
   void output(std::ostream &out) const;
 
 
+PUBLISHED:
+  MAKE_PROPERTY(name, get_name, set_name);
+  MAKE_PROPERTY(value, get_value, set_value);
+  MAKE_PROPERTY(domain, get_domain, set_domain);
+  MAKE_PROPERTY(path, get_path, set_path);
+  MAKE_PROPERTY2(expires, has_expires, get_expires, set_expires, clear_expires);
+  MAKE_PROPERTY(secure, get_secure, set_secure);
+
 private:
 private:
   bool parse_cookie_param(const std::string &param, bool first_param);
   bool parse_cookie_param(const std::string &param, bool first_param);
 
 
@@ -74,9 +92,11 @@ private:
   std::string _path;
   std::string _path;
   std::string _domain;
   std::string _domain;
   HTTPDate _expires;
   HTTPDate _expires;
-  bool _secure;
+  bool _secure = false;
+  SameSite _samesite = SS_unspecified;
 };
 };
 
 
+std::ostream &operator << (std::ostream &out, HTTPCookie::SameSite samesite);
 INLINE std::ostream &operator << (std::ostream &out, const HTTPCookie &cookie);
 INLINE std::ostream &operator << (std::ostream &out, const HTTPCookie &cookie);
 
 
 #include "httpCookie.I"
 #include "httpCookie.I"

+ 3 - 2
panda/src/downloader/httpDigestAuthorization.cxx

@@ -16,6 +16,7 @@
 #ifdef HAVE_OPENSSL
 #ifdef HAVE_OPENSSL
 
 
 #include "httpChannel.h"
 #include "httpChannel.h"
+#include "string_utils.h"
 #include "openSSLWrapper.h"  // must be included before any other openssl.
 #include "openSSLWrapper.h"  // must be included before any other openssl.
 #include <openssl/ssl.h>
 #include <openssl/ssl.h>
 #include <openssl/md5.h>
 #include <openssl/md5.h>
@@ -50,7 +51,7 @@ HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens,
   _algorithm = A_md5;
   _algorithm = A_md5;
   ti = tokens.find("algorithm");
   ti = tokens.find("algorithm");
   if (ti != tokens.end()) {
   if (ti != tokens.end()) {
-    string algo_str = HTTPChannel::downcase((*ti).second);
+    string algo_str = downcase((*ti).second);
     if (algo_str == "md5") {
     if (algo_str == "md5") {
       _algorithm = A_md5;
       _algorithm = A_md5;
     } else if (algo_str == "md5-sess") {
     } else if (algo_str == "md5-sess") {
@@ -63,7 +64,7 @@ HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens,
   _qop = 0;
   _qop = 0;
   ti = tokens.find("qop");
   ti = tokens.find("qop");
   if (ti != tokens.end()) {
   if (ti != tokens.end()) {
-    string qop_str = HTTPChannel::downcase((*ti).second);
+    string qop_str = downcase((*ti).second);
     // A comma-delimited list of tokens.
     // A comma-delimited list of tokens.
 
 
     size_t p = 0;
     size_t p = 0;

+ 20 - 0
panda/src/downloader/virtualFileHTTP.cxx

@@ -140,6 +140,26 @@ open_read_file(bool auto_unwrap) const {
   return return_file(strstream, auto_unwrap);
   return return_file(strstream, auto_unwrap);
 }
 }
 
 
+/**
+ * Fills up the indicated pvector with the contents of the file, if it is a
+ * regular file.  Returns true on success, false otherwise.
+ */
+bool VirtualFileHTTP::
+read_file(vector_uchar &result, bool auto_unwrap) const {
+  if (_status_only) {
+    return false;
+  }
+
+  Ramfile ramfile;
+  if (!_channel->download_to_ram(&ramfile, false)) {
+    return false;
+  }
+
+  const string &data = ramfile.get_data();
+  std::copy(data.begin(), data.end(), std::back_inserter(result));
+  return true;
+}
+
 /**
 /**
  * Downloads the entire file from the web server into the indicated iostream.
  * Downloads the entire file from the web server into the indicated iostream.
  * Returns true on success, false on failure.
  * Returns true on success, false on failure.

+ 2 - 0
panda/src/downloader/virtualFileHTTP.h

@@ -51,6 +51,8 @@ public:
   virtual std::streamsize get_file_size() const;
   virtual std::streamsize get_file_size() const;
   virtual time_t get_timestamp() const;
   virtual time_t get_timestamp() const;
 
 
+  virtual bool read_file(vector_uchar &result, bool auto_unwrap) const;
+
 private:
 private:
   bool fetch_file(std::ostream *buffer_stream) const;
   bool fetch_file(std::ostream *buffer_stream) const;
   std::istream *return_file(std::istream *buffer_stream, bool auto_unwrap) const;
   std::istream *return_file(std::istream *buffer_stream, bool auto_unwrap) const;

+ 5 - 0
panda/src/event/pythonTask.cxx

@@ -624,6 +624,11 @@ do_python_task() {
         return DS_done;
         return DS_done;
       }
       }
 
 
+    } else if (result == Py_None) {
+      // Bare yield means to continue next frame.
+      Py_DECREF(result);
+      return DS_cont;
+
     } else if (DtoolInstance_Check(result)) {
     } else if (DtoolInstance_Check(result)) {
       // We are waiting for an AsyncFuture (eg. other task) to finish.
       // We are waiting for an AsyncFuture (eg. other task) to finish.
       AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);
       AsyncFuture *fut = (AsyncFuture *)DtoolInstance_UPCAST(result, Dtool_AsyncFuture);

+ 1 - 1
panda/src/linmath/lmatrix4_src.I

@@ -1021,7 +1021,7 @@ operator += (const FLOATNAME(LMatrix4) &other) {
 }
 }
 
 
 /**
 /**
- * Performs a memberwise addition between two matrices.
+ * Performs a memberwise subtraction between two matrices.
  */
  */
 INLINE_LINMATH FLOATNAME(LMatrix4) &FLOATNAME(LMatrix4)::
 INLINE_LINMATH FLOATNAME(LMatrix4) &FLOATNAME(LMatrix4)::
 operator -= (const FLOATNAME(LMatrix4) &other) {
 operator -= (const FLOATNAME(LMatrix4) &other) {

+ 2 - 2
samples/roaming-ralph/main.py

@@ -268,7 +268,7 @@ class RoamingRalphDemo(ShowBase):
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
 
 
         for entry in entries:
         for entry in entries:
-            if entry.getIntoNode().getName() == "terrain":
+            if entry.getIntoNode().name == "terrain":
                 self.ralph.setZ(entry.getSurfacePoint(render).getZ())
                 self.ralph.setZ(entry.getSurfacePoint(render).getZ())
 
 
         # Keep the camera at one unit above the terrain,
         # Keep the camera at one unit above the terrain,
@@ -278,7 +278,7 @@ class RoamingRalphDemo(ShowBase):
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
         entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
 
 
         for entry in entries:
         for entry in entries:
-            if entry.getIntoNode().getName() == "terrain":
+            if entry.getIntoNode().name == "terrain":
                 self.camera.setZ(entry.getSurfacePoint(render).getZ() + 1.5)
                 self.camera.setZ(entry.getSurfacePoint(render).getZ() + 1.5)
         if self.camera.getZ() < self.ralph.getZ() + 2.0:
         if self.camera.getZ() < self.ralph.getZ() + 2.0:
             self.camera.setZ(self.ralph.getZ() + 2.0)
             self.camera.setZ(self.ralph.getZ() + 2.0)