コード例 #1
0
class PackageInstaller(DirectObject):
    """ This class is used in a p3d runtime environment to manage the
    asynchronous download and installation of packages.  If you just
    want to install a package synchronously, see
    appRunner.installPackage() for a simpler interface.

    To use this class, you should subclass from it and override any of
    the six callback methods: downloadStarted(), packageStarted(),
    packageProgress(), downloadProgress(), packageFinished(),
    downloadFinished().

    Also see DWBPackageInstaller, which does exactly this, to add a
    DirectWaitBar GUI.

    """

    notify = directNotify.newCategory("PackageInstaller")

    globalLock = Lock()
    nextUniqueId = 1

    # This is a chain of state values progressing forward in time.
    S_initial = 0  # addPackage() calls are being made
    S_ready = 1  # donePackages() has been called
    S_started = 2  # download has started
    S_done = 3  # download is over

    class PendingPackage:
        """ This class describes a package added to the installer for
        download. """

        notify = directNotify.newCategory("PendingPackage")

        def __init__(self, packageName, version, host):
            self.packageName = packageName
            self.version = version
            self.host = host

            # This will be filled in properly by checkDescFile() or
            # getDescFile(); in the meantime, set a placeholder.
            self.package = PackageInfo(host, packageName, version)

            # Set true when the package has finished downloading,
            # either successfully or unsuccessfully.
            self.done = False

            # Set true or false when self.done has been set.
            self.success = False

            # Set true when the packageFinished() callback has been
            # delivered.
            self.notified = False

            # These are used to ensure the callbacks only get
            # delivered once for a particular package.
            self.calledPackageStarted = False
            self.calledPackageFinished = False

            # This is the amount of stuff we have to process to
            # install this package, and the amount of stuff we have
            # processed so far.  "Stuff" includes bytes downloaded,
            # bytes uncompressed, and bytes extracted; and each of
            # which is weighted differently into one grand total.  So,
            # the total doesn't really represent bytes; it's a
            # unitless number, which means something only as a ratio
            # to other packages.  Filled in by checkDescFile() or
            # getDescFile().
            self.downloadEffort = 0

            # Similar, but this is the theoretical effort if the
            # package were already downloaded.
            self.prevDownloadedEffort = 0

        def __cmp__(self, pp):
            """ Python comparision function.  This makes all
            PendingPackages withe same (packageName, version, host)
            combination be deemed equivalent. """
            return cmp((self.packageName, self.version, self.host),
                       (pp.packageName, pp.version, pp.host))

        def getProgress(self):
            """ Returns the download progress of this package in the
            range 0..1. """

            return self.package.downloadProgress

        def checkDescFile(self):
            """ Returns true if the desc file is already downloaded
            and good, or false if it needs to be downloaded. """

            if not self.host.hasCurrentContentsFile():
                # If the contents file isn't ready yet, we can't check
                # the desc file yet.
                return False

            # All right, get the package info now.
            package = self.host.getPackage(self.packageName, self.version)
            if not package:
                self.notify.warning(
                    "Package %s %s not known on %s" %
                    (self.packageName, self.version, self.host.hostUrl))
                return False

            self.package = package
            self.package.checkStatus()

            if not self.package.hasDescFile:
                return False

            self.downloadEffort = self.package.getDownloadEffort()
            self.prevDownloadEffort = 0
            if self.downloadEffort == 0:
                self.prevDownloadedEffort = self.package.getPrevDownloadedEffort(
                )

            return True

        def getDescFile(self, http):
            """ Synchronously downloads the desc files required for
            the package. """

            if not self.host.downloadContentsFile(http):
                return False

            # All right, get the package info now.
            package = self.host.getPackage(self.packageName, self.version)
            if not package:
                self.notify.warning(
                    "Package %s %s not known on %s" %
                    (self.packageName, self.version, self.host.hostUrl))
                return False

            self.package = package
            if not self.package.downloadDescFile(http):
                return False

            self.package.checkStatus()
            self.downloadEffort = self.package.getDownloadEffort()
            self.prevDownloadEffort = 0
            if self.downloadEffort == 0:
                self.prevDownloadedEffort = self.package.getPrevDownloadedEffort(
                )

            return True

    def __init__(self, appRunner, taskChain='default'):
        self.globalLock.acquire()
        try:
            self.uniqueId = PackageInstaller.nextUniqueId
            PackageInstaller.nextUniqueId += 1
        finally:
            self.globalLock.release()

        self.appRunner = appRunner
        self.taskChain = taskChain

        # If we're to be running on an asynchronous task chain, and
        # the task chain hasn't yet been set up already, create the
        # default parameters now.
        if taskChain != 'default' and not taskMgr.hasTaskChain(self.taskChain):
            taskMgr.setupTaskChain(self.taskChain,
                                   numThreads=1,
                                   threadPriority=TPLow)

        self.callbackLock = Lock()
        self.calledDownloadStarted = False
        self.calledDownloadFinished = False

        # A list of all packages that have been added to the
        # installer.
        self.packageLock = RLock()
        self.packages = []
        self.state = self.S_initial

        # A list of packages that are waiting for their desc files.
        self.needsDescFile = []
        self.descFileTask = None

        # A list of packages that are waiting to be downloaded and
        # installed.
        self.needsDownload = []
        self.downloadTask = None

        # A list of packages that were already done at the time they
        # were passed to addPackage().
        self.earlyDone = []

        # A list of packages that have been successfully installed, or
        # packages that have failed.
        self.done = []
        self.failed = []

        # This task is spawned on the default task chain, to update
        # the status during the download.
        self.progressTask = None

        self.accept('PackageInstaller-%s-allHaveDesc' % self.uniqueId,
                    self.__allHaveDesc)
        self.accept('PackageInstaller-%s-packageStarted' % self.uniqueId,
                    self.__packageStarted)
        self.accept('PackageInstaller-%s-packageDone' % self.uniqueId,
                    self.__packageDone)

    def destroy(self):
        """ Interrupts all pending downloads.  No further callbacks
        will be made. """
        self.cleanup()

    def cleanup(self):
        """ Interrupts all pending downloads.  No further callbacks
        will be made. """

        self.packageLock.acquire()
        try:
            if self.descFileTask:
                taskMgr.remove(self.descFileTask)
                self.descFileTask = None
            if self.downloadTask:
                taskMgr.remove(self.downloadTask)
                self.downloadTask = None
        finally:
            self.packageLock.release()

        if self.progressTask:
            taskMgr.remove(self.progressTask)
            self.progressTask = None

        self.ignoreAll()

    def addPackage(self, packageName, version=None, hostUrl=None):
        """ Adds the named package to the list of packages to be
        downloaded.  Call donePackages() to finish the list. """

        if self.state != self.S_initial:
            raise ValueError, 'addPackage called after donePackages'

        host = self.appRunner.getHostWithAlt(hostUrl)
        pp = self.PendingPackage(packageName, version, host)

        self.packageLock.acquire()
        try:
            self.__internalAddPackage(pp)
        finally:
            self.packageLock.release()

    def __internalAddPackage(self, pp):
        """ Adds the indicated "pending package" to the appropriate
        list(s) for downloading and installing.  Assumes packageLock
        is already held."""

        if pp in self.packages:
            # Already added.
            return

        self.packages.append(pp)

        # We always add the package to needsDescFile, even if we
        # already have its desc file; this guarantees that packages
        # are downloaded in the order they are added.
        self.needsDescFile.append(pp)
        if not self.descFileTask:
            self.descFileTask = taskMgr.add(self.__getDescFileTask,
                                            'getDescFile',
                                            taskChain=self.taskChain)

    def donePackages(self):
        """ After calling addPackage() for each package to be
        installed, call donePackages() to mark the end of the list.
        This is necessary to determine what the complete set of
        packages is (and therefore how large the total download size
        is).  None of the low-level callbacks will be made before this
        call. """

        if self.state != self.S_initial:
            # We've already been here.
            return

        # Throw the messages for packages that were already done
        # before we started.
        for pp in self.earlyDone:
            self.__donePackage(pp, True)
        self.earlyDone = []

        self.packageLock.acquire()
        try:
            if self.state != self.S_initial:
                return
            self.state = self.S_ready
            if not self.needsDescFile:
                # All package desc files are already available; so begin.
                self.__prepareToStart()
        finally:
            self.packageLock.release()

        if not self.packages:
            # Trivial no-op.
            self.__callDownloadFinished(True)

    def downloadStarted(self):
        """ This callback is made at some point after donePackages()
        is called; at the time of this callback, the total download
        size is known, and we can sensibly report progress through the
        whole. """

        self.notify.info("downloadStarted")

    def packageStarted(self, package):
        """ This callback is made for each package between
        downloadStarted() and downloadFinished() to indicate the start
        of a new package. """

        self.notify.debug("packageStarted: %s" % (package.packageName))

    def packageProgress(self, package, progress):
        """ This callback is made repeatedly between packageStarted()
        and packageFinished() to update the current progress on the
        indicated package only.  The progress value ranges from 0
        (beginning) to 1 (complete). """

        self.notify.debug("packageProgress: %s %s" %
                          (package.packageName, progress))

    def downloadProgress(self, overallProgress):
        """ This callback is made repeatedly between downloadStarted()
        and downloadFinished() to update the current progress through
        all packages.  The progress value ranges from 0 (beginning) to
        1 (complete). """

        self.notify.debug("downloadProgress: %s" % (overallProgress))

    def packageFinished(self, package, success):
        """ This callback is made for each package between
        downloadStarted() and downloadFinished() to indicate that a
        package has finished downloading.  If success is true, there
        were no problems and the package is now installed.

        If this package did not require downloading (because it was
        already downloaded), this callback will be made immediately,
        *without* a corresponding call to packageStarted(), and may
        even be made before downloadStarted(). """

        self.notify.info("packageFinished: %s %s" %
                         (package.packageName, success))

    def downloadFinished(self, success):
        """ This callback is made when all of the packages have been
        downloaded and installed (or there has been some failure).  If
        all packages where successfully installed, success is True.

        If there were no packages that required downloading, this
        callback will be made immediately, *without* a corresponding
        call to downloadStarted(). """

        self.notify.info("downloadFinished: %s" % (success))

    def __prepareToStart(self):
        """ This is called internally when transitioning from S_ready
        to S_started.  It sets up whatever initial values are
        needed.  Assumes self.packageLock is held.  Returns False if
        there were no packages to download, and the state was
        therefore transitioned immediately to S_done. """

        if not self.needsDownload:
            self.state = self.S_done
            return False

        self.state = self.S_started

        assert not self.downloadTask
        self.downloadTask = taskMgr.add(self.__downloadPackageTask,
                                        'downloadPackage',
                                        taskChain=self.taskChain)

        assert not self.progressTask
        self.progressTask = taskMgr.add(self.__progressTask, 'packageProgress')

        return True

    def __allHaveDesc(self):
        """ This method is called internally when all of the pending
        packages have their desc info. """
        working = True

        self.packageLock.acquire()
        try:
            if self.state == self.S_ready:
                # We've already called donePackages(), so move on now.
                working = self.__prepareToStart()
        finally:
            self.packageLock.release()

        if not working:
            self.__callDownloadFinished(True)

    def __packageStarted(self, pp):
        """ This method is called when a single package is beginning
        to download. """

        self.__callDownloadStarted()
        self.__callPackageStarted(pp)

    def __packageDone(self, pp):
        """ This method is called when a single package has been
        downloaded and installed, or has failed. """

        self.__callPackageFinished(pp, pp.success)
        pp.notified = True

        # See if there are more packages to go.
        success = True
        allDone = True
        self.packageLock.acquire()
        try:
            for pp in self.packages:
                if pp.notified:
                    success = success and pp.success
                else:
                    allDone = False
        finally:
            self.packageLock.release()

        if allDone:
            self.__callDownloadFinished(success)

    def __callPackageStarted(self, pp):
        """ Calls the packageStarted() callback for a particular
        package if it has not already been called, being careful to
        avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not pp.calledPackageStarted:
                self.packageStarted(pp.package)
                self.packageProgress(pp.package, 0)
                pp.calledPackageStarted = True
        finally:
            self.callbackLock.release()

    def __callPackageFinished(self, pp, success):
        """ Calls the packageFinished() callback for a paricular
        package if it has not already been called, being careful to
        avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not pp.calledPackageFinished:
                if success:
                    self.packageProgress(pp.package, 1)
                self.packageFinished(pp.package, success)
                pp.calledPackageFinished = True
        finally:
            self.callbackLock.release()

    def __callDownloadStarted(self):
        """ Calls the downloadStarted() callback if it has not already
        been called, being careful to avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not self.calledDownloadStarted:
                self.downloadStarted()
                self.downloadProgress(0)
                self.calledDownloadStarted = True
        finally:
            self.callbackLock.release()

    def __callDownloadFinished(self, success):
        """ Calls the downloadFinished() callback if it has not
        already been called, being careful to avoid race
        conditions. """

        self.callbackLock.acquire()
        try:
            if not self.calledDownloadFinished:
                if success:
                    self.downloadProgress(1)
                self.downloadFinished(success)
                self.calledDownloadFinished = True
        finally:
            self.callbackLock.release()

    def __getDescFileTask(self, task):
        """ This task runs on the aysynchronous task chain; each pass,
        it extracts one package from self.needsDescFile and downloads
        its desc file.  On success, it adds the package to
        self.needsDownload. """

        self.packageLock.acquire()
        try:
            # If we've finished all of the packages that need desc
            # files, stop the task.
            if not self.needsDescFile:
                self.descFileTask = None

                eventName = 'PackageInstaller-%s-allHaveDesc' % self.uniqueId
                messenger.send(eventName, taskChain='default')

                return task.done
            pp = self.needsDescFile[0]
            del self.needsDescFile[0]
        finally:
            self.packageLock.release()

        # Now serve this one package.
        if not pp.checkDescFile():
            if not pp.getDescFile(self.appRunner.http):
                self.__donePackage(pp, False)
                return task.cont

        # This package is now ready to be downloaded.  We always add
        # it to needsDownload, even if it's already downloaded, to
        # guarantee ordering of packages.

        self.packageLock.acquire()
        try:
            # Also add any packages required by this one.
            for packageName, version, host in pp.package.requires:
                pp2 = self.PendingPackage(packageName, version, host)
                self.__internalAddPackage(pp2)
            self.needsDownload.append(pp)
        finally:
            self.packageLock.release()

        return task.cont

    def __downloadPackageTask(self, task):
        """ This task runs on the aysynchronous task chain; each pass,
        it extracts one package from self.needsDownload and downloads
        it. """

        while True:
            self.packageLock.acquire()
            try:
                # If we're done downloading, stop the task.
                if self.state == self.S_done or not self.needsDownload:
                    self.downloadTask = None
                    self.packageLock.release()
                    yield task.done
                    return

                assert self.state == self.S_started
                pp = self.needsDownload[0]
                del self.needsDownload[0]
            except:
                self.packageLock.release()
                raise
            self.packageLock.release()

            # Now serve this one package.
            eventName = 'PackageInstaller-%s-packageStarted' % self.uniqueId
            messenger.send(eventName, [pp], taskChain='default')

            if not pp.package.hasPackage:
                for token in pp.package.downloadPackageGenerator(
                        self.appRunner.http):
                    if token == pp.package.stepContinue:
                        yield task.cont
                    else:
                        break

                if token != pp.package.stepComplete:
                    pc = PStatCollector(
                        ':App:PackageInstaller:donePackage:%s' %
                        (pp.package.packageName))
                    pc.start()
                    self.__donePackage(pp, False)
                    pc.stop()
                    yield task.cont
                    continue

            # Successfully downloaded and installed.
            pc = PStatCollector(':App:PackageInstaller:donePackage:%s' %
                                (pp.package.packageName))
            pc.start()
            self.__donePackage(pp, True)
            pc.stop()

            # Continue the loop without yielding, so we pick up the
            # next package within this same frame.

    def __donePackage(self, pp, success):
        """ Marks the indicated package as done, either successfully
        or otherwise. """
        assert not pp.done

        if success:
            pc = PStatCollector(':App:PackageInstaller:install:%s' %
                                (pp.package.packageName))
            pc.start()
            pp.package.installPackage(self.appRunner)
            pc.stop()

        self.packageLock.acquire()
        try:
            pp.done = True
            pp.success = success
            if success:
                self.done.append(pp)
            else:
                self.failed.append(pp)
        finally:
            self.packageLock.release()

        eventName = 'PackageInstaller-%s-packageDone' % self.uniqueId
        messenger.send(eventName, [pp], taskChain='default')

    def __progressTask(self, task):
        self.callbackLock.acquire()
        try:
            if not self.calledDownloadStarted:
                # We haven't yet officially started the download.
                return task.cont

            if self.calledDownloadFinished:
                # We've officially ended the download.
                self.progressTask = None
                return task.done

            downloadEffort = 0
            currentDownloadSize = 0
            for pp in self.packages:
                downloadEffort += pp.downloadEffort + pp.prevDownloadedEffort
                packageProgress = pp.getProgress()
                currentDownloadSize += pp.downloadEffort * packageProgress + pp.prevDownloadedEffort
                if pp.calledPackageStarted and not pp.calledPackageFinished:
                    self.packageProgress(pp.package, packageProgress)

            if not downloadEffort:
                progress = 1
            else:
                progress = float(currentDownloadSize) / float(downloadEffort)
            self.downloadProgress(progress)

        finally:
            self.callbackLock.release()

        return task.cont
コード例 #2
0
class Messenger:

    notify = DirectNotifyGlobal.directNotify.newCategory("Messenger")

    def __init__(self):
        """
        One is keyed off the event name. It has the following structure:
            {event1: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]},
             event2: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]}}

        This dictionary allow for efficient callbacks when the messenger
        hears an event.

        A second dictionary remembers which objects are accepting which
        events. This allows for efficient ignoreAll commands.


        Or, for an example with more real data:
            {'mouseDown': {avatar: [avatar.jump, [2.0], 1]}}
        """
        # eventName->objMsgrId->callbackInfo
        self.__callbacks = {}
        # objMsgrId->set(eventName)
        self.__objectEvents = {}
        self._messengerIdGen = 0
        # objMsgrId->listenerObject
        self._id2object = {}

        # A mapping of taskChain -> eventList, used for sending events
        # across task chains (and therefore across threads).
        self._eventQueuesByTaskChain = {}

        # This protects the data structures within this object from
        # multithreaded access.
        self.lock = Lock()

        if __debug__:
            self.__isWatching = 0
            self.__watching = {}
        # I'd like this to be in the __debug__, but I fear that someone will
        # want this in a release build.  If you're sure that that will not be
        # then please remove this comment and put the quiet/verbose stuff
        # under __debug__.
        self.quieting = {
            "NewFrame": 1,
            "avatarMoving": 1,
            "event-loop-done": 1,
            'collisionLoopFinished': 1,
        }  # see def quiet()

    def _getMessengerId(self, object):
        # TODO: allocate this id in DirectObject.__init__ and get derived
        # classes to call down (speed optimization, assuming objects
        # accept/ignore more than once over their lifetime)
        # get unique messenger id for this object
        # assumes lock is held.
        if not hasattr(object, '_MSGRmessengerId'):
            object._MSGRmessengerId = (object.__class__.__name__,
                                       self._messengerIdGen)
            self._messengerIdGen += 1
        return object._MSGRmessengerId

    def _storeObject(self, object):
        # store reference-counted reference to object in case we need to
        # retrieve it later.  assumes lock is held.
        id = self._getMessengerId(object)
        if id not in self._id2object:
            self._id2object[id] = [1, object]
        else:
            self._id2object[id][0] += 1

    def _getObject(self, id):
        return self._id2object[id][1]

    def _getObjects(self):
        self.lock.acquire()
        try:
            objs = []
            for refCount, obj in self._id2object.itervalues():
                objs.append(obj)
            return objs
        finally:
            self.lock.release()

    def _getNumListeners(self, event):
        return len(self.__callbacks.get(event, {}))

    def _getEvents(self):
        return self.__callbacks.keys()

    def _releaseObject(self, object):
        # assumes lock is held.
        id = self._getMessengerId(object)
        if id in self._id2object:
            record = self._id2object[id]
            record[0] -= 1
            if record[0] <= 0:
                del self._id2object[id]

    def accept(self, event, object, method, extraArgs=[], persistent=1):
        """ accept(self, string, DirectObject, Function, List, Boolean)

        Make this object accept this event. When the event is
        sent (using Messenger.send or from C++), method will be executed,
        optionally passing in extraArgs.

        If the persistent flag is set, it will continue to respond
        to this event, otherwise it will respond only once.
        """
        notifyDebug = Messenger.notify.getDebug()
        if notifyDebug:
            Messenger.notify.debug(
                "object: %s (%s)\n accepting: %s\n method: %s\n extraArgs: %s\n persistent: %s"
                % (safeRepr(object), self._getMessengerId(object), event,
                   safeRepr(method), safeRepr(extraArgs), persistent))

        # Make sure that the method is callable
        assert hasattr(
            method,
            '__call__'), ("method not callable in accept (ignoring): %s %s" %
                          (safeRepr(method), safeRepr(extraArgs)))

        # Make sure extraArgs is a list or tuple
        if not (isinstance(extraArgs, list) or isinstance(extraArgs, tuple)
                or isinstance(extraArgs, set)):
            raise TypeError, "A list is required as extraArgs argument"

        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.setdefault(event, {})

            id = self._getMessengerId(object)

            # Make sure we are not inadvertently overwriting an existing event
            # on this particular object.
            if id in acceptorDict:
                # TODO: we're replacing the existing callback. should this be an error?
                if notifyDebug:
                    oldMethod = acceptorDict[id][0]
                    if oldMethod == method:
                        self.notify.warning(
                            "object: %s was already accepting: \"%s\" with same callback: %s()"
                            % (object.__class__.__name__, safeRepr(event),
                               method.__name__))
                    else:
                        self.notify.warning(
                            "object: %s accept: \"%s\" new callback: %s() supplanting old callback: %s()"
                            % (object.__class__.__name__, safeRepr(event),
                               method.__name__, oldMethod.__name__))

            acceptorDict[id] = [method, extraArgs, persistent]

            # Remember that this object is listening for this event
            eventDict = self.__objectEvents.setdefault(id, {})
            if event not in eventDict:
                self._storeObject(object)
                eventDict[event] = None
        finally:
            self.lock.release()

    def ignore(self, event, object):
        """ ignore(self, string, DirectObject)
        Make this object no longer respond to this event.
        It is safe to call even if it was not already accepting
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring: ' %
                (self._getMessengerId(object), ) + safeRepr(event))

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Find the dictionary of all the objects accepting this event
            acceptorDict = self.__callbacks.get(event)
            # If this object is there, delete it from the dictionary
            if acceptorDict and id in acceptorDict:
                del acceptorDict[id]
                # If this dictionary is now empty, remove the event
                # entry from the Messenger alltogether
                if (len(acceptorDict) == 0):
                    del self.__callbacks[event]

            # This object is no longer listening for this event
            eventDict = self.__objectEvents.get(id)
            if eventDict and event in eventDict:
                del eventDict[event]
                if (len(eventDict) == 0):
                    del self.__objectEvents[id]

                self._releaseObject(object)
        finally:
            self.lock.release()

    def ignoreAll(self, object):
        """
        Make this object no longer respond to any events it was accepting
        Useful for cleanup
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring all events' %
                (self._getMessengerId(object), ))

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                for event in eventDict.keys():
                    # Find the dictionary of all the objects accepting this event
                    acceptorDict = self.__callbacks.get(event)
                    # If this object is there, delete it from the dictionary
                    if acceptorDict and id in acceptorDict:
                        del acceptorDict[id]
                        # If this dictionary is now empty, remove the event
                        # entry from the Messenger alltogether
                        if (len(acceptorDict) == 0):
                            del self.__callbacks[event]
                    self._releaseObject(object)
                del self.__objectEvents[id]
        finally:
            self.lock.release()

    def getAllAccepting(self, object):
        """
        Returns the list of all events accepted by the indicated object.
        """
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                return eventDict.keys()
            return []
        finally:
            self.lock.release()

    def isAccepting(self, event, object):
        """ isAccepting(self, string, DirectOject)
        Is this object accepting this event?
        """
        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.get(event)
            id = self._getMessengerId(object)
            if acceptorDict and id in acceptorDict:
                # Found it, return true
                return 1
            # If we looked in both dictionaries and made it here
            # that object must not be accepting that event.
            return 0
        finally:
            self.lock.release()

    def whoAccepts(self, event):
        """
        Return objects accepting the given event
        """
        return self.__callbacks.get(event)

    def isIgnoring(self, event, object):
        """ isIgnorning(self, string, DirectObject)
        Is this object ignoring this event?
        """
        return (not self.isAccepting(event, object))

    def send(self, event, sentArgs=[], taskChain=None):
        """
        Send this event, optionally passing in arguments

        event is usually a string.
        sentArgs is a list of any data that you want passed along to the
            handlers listening to this event.

        If taskChain is not None, it is the name of the task chain
        which should receive the event.  If taskChain is None, the
        event is handled immediately.  Setting a non-None taskChain
        will defer the event (possibly till next frame or even later)
        and create a new, temporary task within the named taskChain,
        but this is the only way to send an event across threads.
        """
        if Messenger.notify.getDebug() and not self.quieting.get(event):
            assert Messenger.notify.debug(
                'sent event: %s sentArgs = %s, taskChain = %s' %
                (event, sentArgs, taskChain))

        self.lock.acquire()
        try:
            foundWatch = 0
            if __debug__:
                if self.__isWatching:
                    for i in self.__watching.keys():
                        if str(event).find(i) >= 0:
                            foundWatch = 1
                            break
            acceptorDict = self.__callbacks.get(event)
            if not acceptorDict:
                if __debug__:
                    if foundWatch:
                        print "Messenger: \"%s\" was sent, but no function in Python listened." % (
                            event, )
                return

            if taskChain:
                # Queue the event onto the indicated task chain.
                from direct.task.TaskManagerGlobal import taskMgr
                queue = self._eventQueuesByTaskChain.setdefault(taskChain, [])
                queue.append((acceptorDict, event, sentArgs, foundWatch))
                if len(queue) == 1:
                    # If this is the first (only) item on the queue,
                    # spawn the task to empty it.
                    taskMgr.add(self.__taskChainDispatch,
                                name='Messenger-%s' % (taskChain),
                                extraArgs=[taskChain],
                                taskChain=taskChain,
                                appendTask=True)
            else:
                # Handle the event immediately.
                self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
        finally:
            self.lock.release()

    def __taskChainDispatch(self, taskChain, task):
        """ This task is spawned each time an event is sent across
        task chains.  Its job is to empty the task events on the queue
        for this particular task chain.  This guarantees that events
        are still delivered in the same order they were sent. """

        while True:
            eventTuple = None
            self.lock.acquire()
            try:
                queue = self._eventQueuesByTaskChain.get(taskChain, None)
                if queue:
                    eventTuple = queue[0]
                    del queue[0]
                if not queue:
                    # The queue is empty, we're done.
                    if queue is not None:
                        del self._eventQueuesByTaskChain[taskChain]

                if not eventTuple:
                    # No event; we're done.
                    return task.done

                self.__dispatch(*eventTuple)
            finally:
                self.lock.release()

        return task.done

    def __dispatch(self, acceptorDict, event, sentArgs, foundWatch):
        for id in acceptorDict.keys():
            # We have to make this apparently redundant check, because
            # it is possible that one object removes its own hooks
            # in response to a handler called by a previous object.
            #
            # NOTE: there is no danger of skipping over objects due to
            # modifications to acceptorDict, since the for..in above
            # iterates over a list of objects that is created once at
            # the start
            callInfo = acceptorDict.get(id)
            if callInfo:
                method, extraArgs, persistent = callInfo
                # If this object was only accepting this event once,
                # remove it from the dictionary
                if not persistent:
                    # This object is no longer listening for this event
                    eventDict = self.__objectEvents.get(id)
                    if eventDict and event in eventDict:
                        del eventDict[event]
                        if (len(eventDict) == 0):
                            del self.__objectEvents[id]
                        self._releaseObject(self._getObject(id))

                    del acceptorDict[id]
                    # If the dictionary at this event is now empty, remove
                    # the event entry from the Messenger altogether
                    if (event in self.__callbacks \
                            and (len(self.__callbacks[event]) == 0)):
                        del self.__callbacks[event]

                if __debug__:
                    if foundWatch:
                        print "Messenger: \"%s\" --> %s%s" % (
                            event, self.__methodRepr(method),
                            tuple(extraArgs + sentArgs))

                #print "Messenger: \"%s\" --> %s%s"%(
                #            event,
                #            self.__methodRepr(method),
                #            tuple(extraArgs + sentArgs))

                # It is important to make the actual call here, after
                # we have cleaned up the accept hook, because the
                # method itself might call accept() or acceptOnce()
                # again.
                assert hasattr(method, '__call__')

                # Release the lock temporarily while we call the method.
                self.lock.release()
                try:
                    method(*(extraArgs + sentArgs))
                finally:
                    self.lock.acquire()

    def clear(self):
        """
        Start fresh with a clear dict
        """
        self.lock.acquire()
        try:
            self.__callbacks.clear()
            self.__objectEvents.clear()
            self._id2object.clear()
        finally:
            self.lock.release()

    def isEmpty(self):
        return (len(self.__callbacks) == 0)

    def getEvents(self):
        return self.__callbacks.keys()

    def replaceMethod(self, oldMethod, newFunction):
        """
        This is only used by Finder.py - the module that lets
        you redefine functions with Control-c-Control-v
        """
        import new
        retFlag = 0
        for entry in self.__callbacks.items():
            event, objectDict = entry
            for objectEntry in objectDict.items():
                object, params = objectEntry
                method = params[0]
                if (type(method) == types.MethodType):
                    function = method.im_func
                else:
                    function = method
                #print ('function: ' + repr(function) + '\n' +
                #       'method: ' + repr(method) + '\n' +
                #       'oldMethod: ' + repr(oldMethod) + '\n' +
                #       'newFunction: ' + repr(newFunction) + '\n')
                if (function == oldMethod):
                    newMethod = new.instancemethod(newFunction, method.im_self,
                                                   method.im_class)
                    params[0] = newMethod
                    # Found it retrun true
                    retFlag += 1
        # didn't find that method, return false
        return retFlag

    def toggleVerbose(self):
        isVerbose = 1 - Messenger.notify.getDebug()
        Messenger.notify.setDebug(isVerbose)
        if isVerbose:
            print "Verbose mode true.  quiet list = %s" % (
                self.quieting.keys(), )

    if __debug__:

        def watch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unwatch
            """
            if not self.__watching.get(needle):
                self.__isWatching += 1
                self.__watching[needle] = 1

        def unwatch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: watch
            """
            if self.__watching.get(needle):
                self.__isWatching -= 1
                del self.__watching[needle]

        def quiet(self, message):
            """
            When verbose mode is on, don't spam the output with messages
            marked as quiet.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unquiet
            """
            if not self.quieting.get(message):
                self.quieting[message] = 1

        def unquiet(self, message):
            """
            Remove a message from the list of messages that are not reported
            in verbose mode.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: quiet
            """
            if self.quieting.get(message):
                del self.quieting[message]

    def find(self, needle):
        """
        return a matching event (needle) if found (in haystack).
        This is primarily a debugging tool.
        """
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print self.__eventRepr(event),
                return {event: self.__callbacks[event]}

    def findAll(self, needle, limit=None):
        """
        return a dict of events (needle) if found (in haystack).
        limit may be None or an integer (e.g. 1).
        This is primarily a debugging tool.
        """
        matches = {}
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print self.__eventRepr(event),
                matches[event] = self.__callbacks[event]
                # if the limit is not None, decrement and
                # check for break:
                if limit > 0:
                    limit -= 1
                    if limit == 0:
                        break
        return matches

    def __methodRepr(self, method):
        """
        return string version of class.method or method.
        """
        if (type(method) == types.MethodType):
            functionName = method.im_class.__name__ + '.' + \
                method.im_func.__name__
        else:
            functionName = method.__name__
        return functionName

    def __eventRepr(self, event):
        """
        Compact version of event, acceptor pairs
        """
        str = event.ljust(32) + '\t'
        acceptorDict = self.__callbacks[event]
        for key, (method, extraArgs, persistent) in acceptorDict.items():
            str = str + self.__methodRepr(method) + ' '
        str = str + '\n'
        return str

    def __repr__(self):
        """
        Compact version of event, acceptor pairs
        """
        str = "The messenger is currently handling:\n" + "=" * 64 + "\n"
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            str += self.__eventRepr(event)
        # Print out the object: event dictionary too
        str += "=" * 64 + "\n"
        for key, eventDict in self.__objectEvents.items():
            object = self._getObject(key)
            str += "%s:\n" % repr(object)
            for event in eventDict.keys():
                str += "     %s\n" % repr(event)

        str += "=" * 64 + "\n" + "End of messenger info.\n"
        return str

    def detailedRepr(self):
        """
        Print out the table in a detailed readable format
        """
        import types
        str = 'Messenger\n'
        str = str + '=' * 50 + '\n'
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            acceptorDict = self.__callbacks[event]
            str = str + 'Event: ' + event + '\n'
            for key in acceptorDict.keys():
                function, extraArgs, persistent = acceptorDict[key]
                object = self._getObject(key)
                if (type(object) == types.InstanceType):
                    className = object.__class__.__name__
                else:
                    className = "Not a class"
                functionName = function.__name__
                str = (str + '\t' + 'Acceptor:     ' + className +
                       ' instance' + '\n\t' + 'Function name:' + functionName +
                       '\n\t' + 'Extra Args:   ' + repr(extraArgs) + '\n\t' +
                       'Persistent:   ' + repr(persistent) + '\n')
                # If this is a class method, get its actual function
                if (type(function) == types.MethodType):
                    str = (str + '\t' + 'Method:       ' + repr(function) +
                           '\n\t' + 'Function:     ' + repr(function.im_func) +
                           '\n')
                else:
                    str = (str + '\t' + 'Function:     ' + repr(function) +
                           '\n')
        str = str + '=' * 50 + '\n'
        return str
コード例 #3
0
class StoryManager(SogalForm):
    """Story controller of Sogal
    Controls the whole story scene.
    Mainly for logic control
    And graphics is on other  
    Attributes:
    gameTextBox: the current GameTextBox, useful for user scripting
    storyView: the current StoryView, useful for user scripting
    """
    script_space = {}
    _currentDump = None
    __destroyed = False
    


    
    
    def __init__(self):
        self.step = 0    #shows how many commands line it had run
        self.scrStack = []
        self.commandList = []
        
        self.__currentPtr = None
        if not runtime_data.RuntimeData.command_ptr:
            self.nextPtr = 0
        else: self.nextPtr = runtime_data.RuntimeData.command_ptr
        
        if not runtime_data.RuntimeData.command_stack:
            runtime_data.RuntimeData.command_stack = self.scrStack
        else: self.scrStack = runtime_data.RuntimeData.command_stack
        
        if not runtime_data.RuntimeData.command_list:
            runtime_data.RuntimeData.command_list = self.commandList
        else: self.commandList = runtime_data.RuntimeData.command_list
                
        
        self._frame = DirectFrame(parent = aspect2d)  # @UndefinedVariable pydev在傲娇而已不用管
        self._frame.setTransparency(TransparencyAttrib.MAlpha)

        

        
        self.storyView = StoryView()
        self.audioPlayer = base.audioPlayer  # @UndefinedVariable pydev在傲娇而已不用管
        self.menu = StoryMenuBar()
        self.gameTextBox = GameTextBox()
        self.textHistory = TextHistory()
        
        self.button_auto = self.menu.addButton(text = 'Auto',state = DGG.NORMAL,command = self.autoPlayButton)
        self.button_history = self.menu.addButton(text = 'History',state = DGG.NORMAL,command = self.showTextHistoryButton)
        self.button_skip = self.menu.addButton(text = 'Skip',state = DGG.DISABLED,command = self.startSkippingButton)
        self.button_lastchoice = self.menu.addButton(text = 'Last Choice',state = DGG.DISABLED,command = self.lastChoice)
        self.button_save = self.menu.addButton(text = 'Save',state = DGG.DISABLED, command = self.saveButton)
        self.button_load = self.menu.addButton(text = 'Load',state = DGG.NORMAL,command = self.loadButton)
        self.button_quicksave = self.menu.addButton(text = 'Quick Save',state = DGG.DISABLED,command = self.quickSaveButton)
        self.button_quickload = self.menu.addButton(text = 'Quick Load',state = DGG.DISABLED,command = self.quickLoadButton)
        self.button_config = self.menu.addButton(text = 'Options', state = DGG.NORMAL, command = self._configButton)
        self.button_title = self.menu.addButton(text = 'Title',state = DGG.NORMAL,command = self.returnToTitle)
        
        self._inputReady = True
        self.__arrow_shown = False
        self._choiceReady = True
        self._currentMessage = ''
        self.__currentSelection = None
        self.__finishing = False
        self.__lock = Lock()
        self.forcejump = False
        self.__autoInput = False
        self.__focused = False
        self.intervals = []
        self.__skipping = False
        self.__autoplaying = False
        self.__autoInterval = None
        self.__autoplaystep = None
        
        self.mapScriptSpace()
        SogalForm.__init__(self)
        self.show()
        taskMgr.add(self.loopTask,'story_manager_loop',sort = 2,priority = 1)  # @UndefinedVariable 傲娇的pydev……因为panda3D的"黑魔法"……
      
        taskMgr.doMethodLater(runtime_data.game_settings['jump_span'],self.__jumpCheck,'story_jump_check', sort = 5, priority = 1)  # @UndefinedVariable
        
        
        
        
        
    def focused(self):
        if not self.__focused:
            self.accept('mouse1', self.input, [1])
            self.accept('enter', self.input, [1])
            self.accept('mouse3', self.input, [3])
            self.accept('wheel_up', self.showTextHistory)
            self.accept('escape', self.showMenu)
            self.accept('control',self.setForceJump, [True])
            self.accept('control-up',self.setForceJump, [False])
            SogalForm.focused(self)
            self.__focused = True
    
    
    def defocused(self):
        if self.__focused:
            self.ignore('mouse1')
            self.ignore('enter')
            self.ignore('mouse3')
            self.ignore('wheel_up')
            self.ignore('escape')
            self.ignore('control')
            self.ignore('control-up')
            self.setForceJump(False)
            SogalForm.defocused(self)
            self.__arrow_shown = False
            self.gameTextBox.hideArrow()
            self.__focused = False
   
        
    def destroy(self):
        self.__destroyed = True
        taskMgr.remove('story_manager_loop')  # @UndefinedVariable
        taskMgr.remove('story_jump_check')  # @UndefinedVariable
        if self._frame:
            self._frame.destroy()
            self._frame = None
        
        if self.__currentSelection:
            self.__currentSelection.destroy()
        self.gameTextBox.destroy()
        self.storyView.destroy()
        self.menu.destroy()
        self.textHistory.destroy()
        SogalForm.destroy(self)
            
    def loopTask(self,task):
        '''
        The task loop of StoryManager, trying to advance every task frame
        '''
        if not self.__destroyed:
            if self.hasFocus():
                self.forward(False)
            return task.cont
        else: return task.done


        
    def _enableSavingButton(self):
        self.button_save['state'] = DGG.NORMAL
        self.button_quicksave['state'] = DGG.NORMAL

    def _disableSavingButton(self):
        self.button_save['state'] = DGG.DISABLED
        self.button_quicksave['state'] = DGG.DISABLED
    
        
    def presave(self):
        if self.nextPtr is not None:
            runtime_data.RuntimeData.command_ptr = self.nextPtr
        self.gameTextBox.presave()
        self.storyView.presave()
        self.audioPlayer.presave()
        
    def reload(self):
        taskMgr.remove('storyManagerLoop')  # @UndefinedVariable
        self.nextPtr = runtime_data.RuntimeData.command_ptr
        self.mapScriptSpace()
        self.gameTextBox.reload()
        self.storyView.reload()
        self.audioPlayer.reload()
        taskMgr.add(self.loopTask,'storyManagerLoop',sort = 2,priority = 1)  # @UndefinedVariable 傲娇的pydev……因为panda3D的"黑魔法"……
       
      
    def mapScriptSpace(self):
        if runtime_data.RuntimeData.script_space:  #map script space
            self.script_space = runtime_data.RuntimeData.script_space
        else: runtime_data.RuntimeData.script_space = self.script_space 
        script_global['goto'] = self.goto
        script_global['story_manager'] = self    
        script_global['game_text_box'] = self.gameTextBox
        script_global['story_view'] = self.storyView
        script_global['audio_player'] = self.audioPlayer
        
    def quickfinish(self):
        self.__lock.acquire()
        if not self.__finishing:
            self.__finishing = True
            self.storyView.quickfinish()
            self.gameTextBox.quickFinish()
            for itv in self.intervals:
                itv.finish()
        self.__lock.release()
    
    def input(self,type = 1):
        self.stopSkipping()
        self.stopAutoPlaying()
        
        if not self.hasFocus():
            return
        #left mouse button or enter key
        if type == 1 :
            if not self.getSceneReady():
                self.quickfinish()
            else:
                self.setTextInputReady(True)
        #right mouse button
        elif type == 3:
            self.quickfinish()
            if self.getSceneReady():
                self.menu.show()
                
    def showMenu(self):
        self.stopSkipping()
        self.stopAutoPlaying()
        
        self.quickfinish()
        if self.getSceneReady() and not self.forcejump:
            self.menu.show()   
            
    def showTextHistory(self):
        if self.getSceneReady() and not self.forcejump:
            self.textHistory.show()
                
    def saveButton(self):
        self.menu.hide()
        base.saveForm.setData(self._currentDump, self._currentMessage)
        base.saveForm.show()
        
    def loadButton(self):
        self.menu.hide()
        base.loadForm.show()
            
    def quickSaveButton(self):
        '''quicksave the data'''
        self.menu.hide()
        self.button_quicksave['state'] = DGG.DISABLED
        self.button_quickload['state'] = DGG.DISABLED
        if self._currentDump:
            messenger.send('quick_save', [self._currentDump,self._currentMessage])
            
    def quickLoadButton(self):
        ConfirmDialog(text= '要读取吗?',command= self.__confirmedQuickLoad)
        
    def _configButton(self):
        messenger.send('config_form')
        
    def returnToTitle(self):
        ConfirmDialog(text= '回到标题界面?',command= self.__confirmedReturnToTitle)
    
    def __confirmedReturnToTitle(self):
        messenger.send('return_to_title')
        
    def showTextHistoryButton(self):
        self.menu.hide()
        self.showTextHistory()
        
    def startSkippingButton(self):
        self.menu.hide()
        self.startSkipping()
        
    def autoPlayButton(self):
        self.menu.hide()
        if self.__autoplaying:
            self.stopAutoPlaying()
        else: self.startAutoPlaying()
        
    def lastChoice(self):
        ConfirmDialog(text= '要回到上一个选择枝吗?',command= self.__confirmedLastChoice)
        
    def __confirmedQuickLoad(self):
            messenger.send('quick_load')  # @UndefinedVariable        
    
    def __confirmedLastChoice(self):
        if runtime_data.RuntimeData.last_choice:
            messenger.send('load_memory',[runtime_data.RuntimeData.last_choice])            # @UndefinedVariable
            
    def autoSave(self,info = ''):
        if not info:
            info = self._currentMessage
        messenger.send('auto_save',[self._currentDump,info])
    
    def getSceneReady(self):
        '''Get if the scene is ready'''
        textbox_ready = False
        view_ready = False
        intervals_ready = True
        
        if not self.gameTextBox.getIsWaiting():
            textbox_ready = True
            
        if not self.storyView.getIsWaiting():
            view_ready = True
            
        for itv in self.intervals:
            if itv.isPlaying():
                intervals_ready = False
                break
            
        scene_ready = textbox_ready and view_ready and intervals_ready
        
        if not scene_ready:
            return False
        
        #auto play span
        if scene_ready and self.__autoplaying:
            if self.__autoplaystep != self.step:
                self.__autoplaystep = self.step
                self.__autoInterval = Wait(runtime_data.game_settings['auto_span'])
                self.__autoInterval.start()
                scene_ready = False
            else:
                if self.__autoInterval.isPlaying():
                    scene_ready = False
                    
            if self.audioPlayer.isVoicePlaying():
                scene_ready = False
        
        return scene_ready
    
    def getInputReady(self):
        '''define is user's 'next' command given'''
        textinput_ready = self._inputReady or self.__autoInput
        choice_ready = self.getChoiceReady()
        
        if self.__autoInput:
            self.__autoInput = False
        
        if textinput_ready and choice_ready:
            return True
        return False
    
    def setTextInputReady(self, value):
        self._inputReady = value
        
    def getChoiceReady(self):
        return self._choiceReady
    

    def forward(self,is_user = False):
        '''run nextCommand() or finish current operations quickly
        @param is_user: define if this operation is started by the player 
        '''
        scene_ready = self.getSceneReady()
            
            
        if scene_ready and not self.__arrow_shown:
            self.gameTextBox.showArrow()
            self.__arrow_shown = False
        
        if scene_ready and self.getInputReady():
            self.nextCommand()
            
        if self.forcejump or self.__skipping:
            self.quickfinish()
        
    def nextCommand(self):
        '''Process the next command in the non-processed queue
        '''
        #TODO: 还要添加循环的支持,用到runtime_data.command_stack来暂存需要回跳的命令组和回跳节点
        #TODO:添加对条件和选择的支持
        self.__finishing = False
        
        self.__arrow_shown = False
        self.gameTextBox.hideArrow()
        
        #Dumps story data for saving or further use
        if self.__destroyed:
            return
        
         
        self.presave()
        self._currentDump = copy.deepcopy(runtime_data.RuntimeData)
        
        if self.nextPtr < 0: self.nextPtr = 0
        self.__currentPtr = self.nextPtr
        self.nextPtr += 1
        
        if not self.commandList:
            return
        
        if len(self.commandList) > self.__currentPtr:
            handled = False
            if self.commandList[self.__currentPtr].command:
                comline = self.commandList[self.__currentPtr].command.strip()
                if comline.startswith('mark ')or comline.startswith('mark:'):
                    handled = True
                    
                # 条件判断处理 If condition
                elif comline.startswith('if '):
                    splited = space_cutter.split(comline, 1)
                    if len(splited)<2:
                        raise Exception('没条件玩毛线')
                    if self.scriptEval(splited[1]):
                        handled = True
                        
                    else:
                        relative_depth = 0
                        #if not match the condition, try to jump to elif or else
                        for i in range(self.__currentPtr+1,len(self.commandList)):
                            cli = self.commandList[i]
                            if cli.command:
                                cl = cli.command.strip()
                            else: continue
                            #一个嵌套循环的情况! A inner if
                            if cl.startswith('if '):
                                relative_depth += 1
                                continue
                            elif relative_depth == 0 and cl.startswith('elif '):
                                splited = space_cutter.split(cl, 1)
                                if len(splited)<2:
                                    raise Exception('没条件玩毛线')
                                if self.scriptEval(splited[1]):
                                    self.nextPtr = i + 1
                                    handled = True
                                    break
                                else: continue
                            elif relative_depth == 0 and cl == 'else':
                                self.nextPtr = i + 1
                                handled = True
                                break
                            elif cl == 'end' or cl.startswith('end '):
                                if relative_depth == 0:
                                    self.nextPtr = i + 1
                                    handled = True
                                    break
                                else: 
                                    relative_depth -= 1
                                    continue
                                    
                #if we meet else or elif then jump to end
                elif comline.startswith('elif ') or comline == 'else':

                    relative_depth = 0
                    for i in range(self.__currentPtr+1,len(self.commandList)):
                        cli = self.commandList[i]
                        if cli.command:
                            cl = cli.command.strip()
                        else: continue
                        if cl.startswith('if '):
                            relative_depth += 1
                            continue
                        elif cl == 'end' or cl.startswith('end '):
                            if relative_depth == 0:
                                self.nextPtr = i + 1
                                handled = True
                                break
                            else: 
                                relative_depth -= 1
                                continue                        
                
                #ignore end
                elif comline == 'end' or comline.startswith('end '):
                    handled = True
                
                                     
            
            if not handled:
                self.processCommand(self.commandList[self.__currentPtr])
            
            #self.scrPtr = self.scrPtr + 1
            #runtime_data.RuntimeData.command_ptr = self.scrPtr
        
        
        if base.hasQuickData():
            self.button_quickload['state'] = DGG.NORMAL 
            
        if runtime_data.RuntimeData.last_choice:
            self.button_lastchoice['state'] = DGG.NORMAL 
            

            
            
        if self._currentDump: 
            self._enableSavingButton()
            
        self.step += 1  #mark step
    
    def goto(self, target):
        '''Jump to a mark'''
        for i in range(0, len(self.commandList)):
            if self.commandList[i].command:
                mark = mark_cutter.split(self.commandList[i].command , 1)
                if len(mark) > 1:
                    markText = mark[1].strip()
                    if markText == target:
                        self.nextPtr = i    #Solved: #this is not a good solution but this method runs at 'nextCommand', and ths scrPtr would plus 1 afterwards
                        return
        safeprint('unable to find mark')

    def processCommand(self,command):
        '''Process a StoryCommand
        @param command: The StoryCommand to deal with
        '''    
        
        
        def seval(strs):
            return self.scriptEval(strs)
        
        
        #Mark read
        if not runtime_data.read_text.has_key(command.fileLoc):
            runtime_data.read_text[command.fileLoc] = {}
        if not runtime_data.read_text[command.fileLoc].has_key(command.index):
            already_read = False
            self.stopSkipping()
        else: already_read = True
        runtime_data.read_text[command.fileLoc][command.index] = True
        if already_read:
            self.button_skip['state'] = DGG.NORMAL
        else: self.button_skip['state'] = DGG.DISABLED
        
        self.storyView.clearQuickItems()  #clear out quick items
        
        name = ''
        continuous = False
        is_script = False
        is_selection = False
        spaceCutter = space_cutter
        
        hidingtext = False

        voiceFlag = False                   #it will be True if voice is stopped in this line of command 
                                            #used for disable cross voice of different command lines
                                            #but enable one command line with multiple voices
                                            
        autoSaving = False                  #Mark if autosaving is needed at the end of this step
        autoSavingInfo = ''
        
        #read command line
        if command.command:
            commands = command.command.split(',')
            comm = ''
            for item in commands:
                comm = item.strip()
                if comm:
                    messenger.send('sogalcommand',[comm]) #allow other tools to deal with it @UndefinedVariable
                #名字设置命令
                if comm.startswith('name ') or comm.startswith('name='): 
                    nameCutter = re.compile(ur'name *=?',re.UNICODE) 
                    name = nameCutter.split(comm,1)[1].strip()
        
                #改变文本框格式命令
                elif comm.startswith('textboxstyle '):
                    if self.gameTextBox:
                        self.gameTextBox.setTextBoxStyle(spaceCutter.split(comm, 1)[1])
                        self.gameTextBox.applyStyle()
                
                #文本框分段命令
                elif comm == 'p':
                    if self.gameTextBox:
                        self.gameTextBox.paragraphSparator()
                        
                elif comm == 'c':
                    continuous = True
                    
                elif comm.startswith('wait '):
                    temp = spaceCutter.split(comm,1)
                    if len(temp) > 1:
                        self.sceneWait(seval(temp[1]))
                
                #文本框属性设置命令
                elif comm.startswith('textbox '):
                    temp = spaceCutter.split(comm,2)
                    if temp[1] == 'apply':
                        self.gameTextBox.applyTextBoxProperties()
                    elif len(temp)>=3:
                        self.gameTextBox.setTextBoxProperty(temp[1],seval(temp[2]))
                    else:
                        safeprint('Not enough: ' + comm)
                        
                #背景设置命令
                elif comm.startswith('bg '):
                    temp = spaceCutter.split(comm,2)
                    if len(temp) >= 3:
                        fadein = seval(temp[2])
                    else: fadein = 0
                    self.storyView.changeBackground(temp[1],fadein)
                
                #图片显示命令
                elif comm.startswith('p '):
                    temp = spaceCutter.split(comm,6)
                    if len(temp) >= 7:
                        fadein = seval(temp[6])
                    else:
                        fadein = 0
                    if len(temp) >= 6:
                        scale = seval(temp[5])
                    else:
                        scale = 1
                    if len(temp) >= 5:
                        location = (seval(temp[3]),0,seval(temp[4]))
                    else:
                        if self.storyView.itemEntries.has_key(temp[1]):
                            location = self.storyView.itemEntries[temp[1]].pos
                        else: location = (0,0,0)
                    svie = StoryViewItemEntry(temp[1],temp[2],SVIC.FG,pos = location,scale = (scale,scale,scale),color = (1,1,1,1),fadein = fadein)
                    self.storyView.newItem(svie)
                    
                elif comm.startswith('del '):
                    temp = spaceCutter.split(comm,2)
                    if len(temp)>=3:
                        self.storyView.deleteItem(temp[1], seval(temp[2]))
                    else:
                        self.storyView.deleteItem(temp[1])
                    
                elif comm.startswith('ploc '):
                    temp = spaceCutter.split(comm,5)
                    if len(temp) >= 5:
                        location =  (seval(temp[2]),seval(temp[3]),seval(temp[4]))
                    else:
                        location =  (seval(temp[2]),0,seval(temp[3]))
                    if len(temp) >= 6:
                        fadein = seval(temp[5])
                    else: fadein = 0
                    self.storyView.changePosColorScale(temp[1], pos = location,time = fadein)
                    
                elif comm.startswith('pcolor '):
                    temp = spaceCutter.split(comm,6)
                    color = (seval(temp[2]),seval(temp[3]),seval(temp[4]),seval(temp[5]))
                    if len(temp) >= 7:
                        fadein = seval(temp[6])
                    else: fadein = 0
                    self.storyView.changePosColorScale(temp[1], color = color, time = fadein)
                    
                elif comm.startswith('pscale '):
                    temp = spaceCutter.split(comm,5)
                    if len(temp) >= 5:
                        scale = (seval(temp[2]),seval(temp[3]),seval(temp[4]))
                    else: scale = (seval(temp[2]),seval(temp[2]),seval(temp[2]))
                    if len(temp) == 6:
                        fadein = seval(temp[5])
                    elif len(temp) == 4:
                        fadein = seval(temp[3])
                    else: fadein = 0
                    self.storyView.changePosColorScale(temp[1], scale = scale, time = fadein)
                
                elif comm.startswith('o3d '):
                    temp = spaceCutter.split(comm)
                    svie = StoryViewItemEntry(temp[1],temp[2],SVIC.O3D,pos = (seval(temp[3]),seval(temp[4]),seval(temp[5]))
                                              ,scale = (seval(temp[10]),seval(temp[11]),seval(temp[12])),color = (seval(temp[6]),seval(temp[7]),seval(temp[8]),seval(temp[9])))
                    self.storyView.newItem(svie)
                
                elif comm.startswith('o2d '):
                    temp = spaceCutter.split(comm)
                    svie = StoryViewItemEntry(temp[1],temp[2],SVIC.O2D,pos = (seval(temp[3]),seval(temp[4]),seval(temp[5]))
                                              ,scale = (seval(temp[10]),seval(temp[11]),seval(temp[12])),color = (seval(temp[6]),seval(temp[7]),seval(temp[8]),seval(temp[9])))
                    self.storyView.newItem(svie)
                    
                elif comm.startswith('pa '):
                    temp = spaceCutter.split(comm)
                    if len(temp) >= 8:
                        fadein = seval(temp[7])
                    else: fadein = 0
                    svie = StoryViewItemEntry(temp[1],temp[2],SVIC.AFG,pos = (seval(temp[3]),0,seval(temp[4]))
                                              ,scale = (seval(temp[5]),1,seval(temp[6])),fadein = fadein)
                    self.storyView.newItem(svie)
                
                elif comm == 'clear':
                    hidingtext = True
                    self.storyView.clear()
                    
                elif comm.startswith('clear '):
                    hidingtext = True
                    temp = spaceCutter.split(comm,2)
                    if len(temp)>=3:
                        self.storyView.clear(seval(temp[1]),temp[2])
                    elif len(temp)==2:
                        self.storyView.clear(seval(temp[1]))
                    else:
                        self.storyView.clear()
                        
                elif comm.startswith('delbg '):
                    temp = spaceCutter.split(comm,1)
                    if len('temp')>=2:
                        self.storyView.deleteItem('__bg__', seval(temp[1]))
                    else:
                        self.storyView.deleteItem('__bg__')
                        
                elif comm.startswith('qp '):
                    temp = spaceCutter.split(comm,3)
                    svie = StoryViewItemEntry('quickitem',temp[1],SVIC.FG,
                                              pos = (seval(temp[2]),0,seval(temp[3])),
                                              quickitem = True
                                              )
                    self.storyView.newItem(svie)
                
                elif comm.startswith('v '):
                    if not voiceFlag:
                        self.audioPlayer.stopVoice()
                        voiceFlag = True
                    temp = spaceCutter.split(comm , 2)
                    if len(temp)>=3:
                        volume = seval(temp[2])
                    else: volume = 1
                    self.audioPlayer.playVoice(temp[1],volume)
                    
                elif comm.startswith('se '):
                    temp = spaceCutter.split(comm , 2)
                    if len(temp)>=3:
                        volume = seval(temp[2])
                    else: volume = 1
                    self.audioPlayer.playSound(temp[1],volume)
                    
                elif comm == 'vstop':
                    self.audioPlayer.stopVoice()
                    voiceFlag = True
                    
                elif comm == 'sestop':
                    self.audioPlayer.stopSound()
                    
                elif comm.startswith('bgm '):
                    temp = spaceCutter.split(comm , 4)
                    if len(temp)>=3:
                        fadein = seval(temp[2])
                    else: fadein = 0
                    if len(temp)>=4:
                        volume = seval(temp[3])
                    else: volume = 1
                    if len(temp)>=5:
                        loop = bool(seval(temp[4]))
                    else: loop = True
                    self.audioPlayer.playBGM(temp[1], fadein=fadein, volume=volume, loop=loop)      
                    
                elif comm.startswith('env '):
                    temp = spaceCutter.split(comm , 4)
                    if len(temp)>=3:
                        fadein = seval(temp[2])
                    else: fadein = 0
                    if len(temp)>=4:
                        volume = seval(temp[3])
                    else: volume = 1
                    if len(temp)>=5:
                        loop = bool(seval(temp[4]))
                    else: loop = True
                    self.audioPlayer.playENV(temp[1], fadein=fadein, volume=volume, loop=loop)        
                
                elif comm.startswith('bgmstop ') or comm == 'bgmstop':
                    temp = spaceCutter.split(comm , 1)
                    if len(temp)>=2:
                        fadeout = seval(temp[1])
                    else: fadeout = 0
                    self.audioPlayer.stopBGM(fadeout)
                    
                elif comm.startswith('envstop ') or comm == 'envstop':
                    temp = spaceCutter.split(comm , 1)
                    if len(temp)>=2:
                        fadeout = seval(temp[1])
                    else: fadeout = 0
                    self.audioPlayer.stopENV(fadeout)
                    
                elif comm.startswith('audiostop ') or comm == 'audiostop':
                    temp = spaceCutter.split(comm , 1)
                    if len(temp)>=2:
                        fadeout = seval(temp[1])
                    else: fadeout = 0
                    self.audioPlayer.stopAll(fadeout)
                    
                elif comm == 'script':
                    is_script = True
                    
                elif comm.startswith('script '):
                    temp = spaceCutter.split(comm , 1)
                    self.runScriptFile(temp[1])    
                    
                elif comm == 'choice' or comm.startswith('choice '):
                    '''
                    Selection
                    '''
                    is_selection = True
                    temp = spaceCutter.split(comm, 1)
                    if len(temp) > 1:
                        striped = temp[1].strip()
                        if striped:
                            self.pushText(text = striped, speaker = None, needInput = False, read = already_read)
                            
                elif comm.startswith('jump '):
                    temp = spaceCutter.split(comm , 1)
                    self.beginScene(temp[1].strip())
                    
                elif comm.startswith('expand '):
                    temp = spaceCutter.split(comm , 1)
                    self.expandScene(temp[1].strip())
                    
                elif comm.startswith('goto '):
                    temp = spaceCutter.split(comm , 1)
                    self.goto(temp[1].strip())       
                    
                elif comm.startswith('theme '):
                    temp = spaceCutter.split(comm , 1)
                    self.reloadTheme(temp[1].strip())
               
                elif comm == 'autosave' or comm.startswith('autosave '):
                    if self.step > 0:
                        autoSaving = True
                        temp = spaceCutter.split(comm , 1)
                        if len(temp) > 1:
                            autoSavingInfo = temp[1]
        
                else: 
                    if comm:
                        safeprint('extra command: ' + comm)
                        
        if command.text:
            if is_script:
                self.runScript(command.text)
            
            elif is_selection:
                '''
                If encountered a selection
                '''
                choiceList = []
                enablesList = []
                textlines = command.text.splitlines()
                for tl in textlines:
                    if tl.startswith('--'):  #--means disabled
                        text = tl[2:]
                        enablesList.append(False)
                    else:
                        text = tl
                        enablesList.append(True)
                    choiceList.append(text)
                self.showSelection(choiceList = choiceList, enablesList = enablesList)
            
            else:
                self.pushText(text = command.text, speaker = name, continuous = continuous, read = already_read)
            
        else:
            if hidingtext:
                self.gameTextBox.hide()    #better to hide the textbox when 'vclear'
                
        if self.gameTextBox.newText:
            runtime_data.RuntimeData.latest_text = self.gameTextBox.newText
        if runtime_data.RuntimeData.latest_text:
            self._currentMessage = runtime_data.RuntimeData.latest_text
        
        #Autosave
        if autoSaving:
            if autoSavingInfo:
                self.autoSave(autoSavingInfo)
            else: self.autoSave()

                
    
    def pushText(self, text, speaker = None, continuous = False, needInput = True, read = False):
        
        def translate(t):
            '''
            ,实现通配,__代替空行已经在一开始实现
            '''
            return t.replace(ur'\:', ur':').replace(ur'\:',ur':').replace(ur'\#',ur'#')
        #检查有无在文本中的name
        #name: formation checking
        textlines = text.splitlines()
        first_line = unicode(textlines[0])
        
        #匹配第一行中是否有表示name的冒号,正则表达式表示前面不是\的冒号(@name 命令行的简写形式判断)
        pattern = re.compile(ur'(?<!\\)[:(:)]',re.UNICODE)  
        
        splited = pattern.split(first_line,maxsplit = 1)
        #print(splited)    #测试用,废弃
        
        #如果存在name即分割成功
        if len(splited)>1:
            speaker = translate(splited[0]).strip()
            if splited[1].strip():
                textlines[0] = splited[1]
            else:
                textlines[0] = None
                
        final_text = ''

        #生成文本并解决转义符
        #Generate the final text
        for item in textlines:
            if item:
                final_text += translate(item) + '\n'
                
        if final_text:
            self.textHistory.append(final_text, speaker, None)
        
        self.gameTextBox.pushText(text = final_text, speaker = speaker, continuous = continuous,read= read)
        if needInput:
            self._inputReady = False
        self.gameTextBox.show()        
    
    def runScript(self,pscriptText):
        exec(pscriptText,script_global,self.script_space)
    
    def runScriptFile(self,fileName):
        #'''note that would ignore panda virtual pathes'''
        pathes = runtime_data.game_settings['pscriptpathes']
        types = runtime_data.game_settings['pscripttypes']
        for ft in ((folder,type) for folder in pathes for type in types):
            if exists(ft[0] + fileName + ft[1]):
                handle = open(ft[0] + fileName + ft[1])
                script = handle.read()
                handle.close()
                break
        if script:
            self.runScript(script)
        else:
            safeprint("file not find: "+ fileName) 
        
    def beginScene(self,fileName):
        '''Load target .sogal script file and go to that file.
        '''
        self.nextPtr = 0 
        self.scrStack = []  #used for stack controller pointers
        self.commandList = loadScriptData(fileName)
        runtime_data.RuntimeData.command_stack = self.scrStack
        runtime_data.RuntimeData.command_list = self.commandList
        
    def expandScene(self,fileName):
        '''expand a scene, inserting another sogal file in to current point'''
        expanding = loadScriptData(fileName)
        if len(self.commandList)> self.__currentPtr+1:
            self.commandList = self.commandList[:self.__currentPtr] + expanding + self.commandList[self.__currentPtr+1:]
        else:
            self.commandList = self.commandList[:self.__currentPtr] + expanding
        runtime_data.RuntimeData.command_stack = self.scrStack
        runtime_data.RuntimeData.command_list = self.commandList
        self.nextPtr = self.__currentPtr    #Solved: # this is not a good solution but this method runs at 'nextCommand', and ths scrPtr would plus 1 afterwards
        
    def showSelection(self,choiceList = ['A','B'],enablesList = None):
        '''This method shows a selection, which sets 'last_choice' in 
        script space to player's choice. (0 is the first, 1 is the second etc.)
        you can disable some of the selections with enablesList
        for example for choiceList ['A','B','C'] and enablesList
        '''
        #Store the last selection
        rdc = copy.deepcopy(self._currentDump)
        rdc.last_choice = None
        self.__tempDumpedLastChoice = pickle.dumps(rdc, 2)

        self._choiceReady = False
        startPos = (0,0,0.1 * len(choiceList))
        frameSize = (-0.6,0.6,-0.05 - 0.1*len(choiceList), 0.1 + 0.1*len(choiceList))
        buttonSize = (-0.5,0.5,-0.050,0.10)
        margin = 0.05
        self.__currentSelection = SogalDialog(enableMask = False, fadeScreen= None, command = self.__selected, 
                                              textList= choiceList, enablesList= enablesList, sortType= 1,noFocus = True,
                                              startPos = startPos,frameSize = frameSize,margin = margin,
                                              buttonSize = buttonSize)
        
        
        
    def __selected(self,choice):
        #Store the last selection
        if self.__tempDumpedLastChoice:
            runtime_data.RuntimeData.last_choice = self.__tempDumpedLastChoice 
        
        self.script_space['last_choice'] = choice
        self._choiceReady = True
        
    def scriptEval(self,str):
        return eval(str,script_global,runtime_data.RuntimeData.script_space)
    
    def setForceJump(self,forcejump):
        if forcejump:
            if not self.forcejump:
                self.forcejump = True
                self.setTextInputReady(True)
                self.__skipping = False
                self.stopAutoPlaying()
        else:
            self.forcejump = False
            
    def __jumpCheck(self,task):
        if self.hasFocus():
            if self.forcejump or self.__skipping or self.__autoplaying:
                self.__autoInput = True
        return task.again
    
    def sceneWait(self,time,hidetextbox = True):
        waitinterval = Wait(time)
        self.intervals.append(waitinterval)
        waitinterval.start()
        if hidetextbox:
            self.gameTextBox.hide()
            
    def reloadTheme(self,theme):
        base.setStyle(theme)
        self.menu.reloadTheme()
        self.textHistory.reloadTheme()
        self.gameTextBox.reloadTheme()
        
    def stopSkipping(self):
        self.__skipping = False
        if not self.forcejump:
            self.__autoInput = False
        
    def startSkipping(self):
        self.stopAutoPlaying()
        self.__skipping = True
        
    def startAutoPlaying(self):
        self.__skipping = False
        self.__autoplaying = True
        self.button_auto['text'] = 'Stop Auto'
        
    def stopAutoPlaying(self):
        self.__autoplaying = False
        if self.__autoInterval:
            self.__autoInterval.pause()
        self.button_auto['text'] = 'Auto'
コード例 #4
0
class Messenger:
    notify = DirectNotifyGlobal.directNotify.newCategory('Messenger')

    def __init__(self):
        self.__callbacks = {}
        self.__objectEvents = {}
        self._messengerIdGen = 0
        self._id2object = {}
        self._eventQueuesByTaskChain = {}
        self.lock = Lock()
        self.quieting = {
            'NewFrame': 1,
            'avatarMoving': 1,
            'event-loop-done': 1,
            'collisionLoopFinished': 1
        }

    def _getMessengerId(self, object):
        if not hasattr(object, '_MSGRmessengerId'):
            object._MSGRmessengerId = (object.__class__.__name__,
                                       self._messengerIdGen)
            self._messengerIdGen += 1
        return object._MSGRmessengerId

    def _storeObject(self, object):
        id = self._getMessengerId(object)
        if id not in self._id2object:
            self._id2object[id] = [1, object]
        else:
            self._id2object[id][0] += 1

    def _getObject(self, id):
        return self._id2object[id][1]

    def _getObjects(self):
        self.lock.acquire()
        try:
            objs = []
            for refCount, obj in self._id2object.itervalues():
                objs.append(obj)

            return objs
        finally:
            self.lock.release()

    def _getNumListeners(self, event):
        return len(self.__callbacks.get(event, {}))

    def _getEvents(self):
        return self.__callbacks.keys()

    def _releaseObject(self, object):
        id = self._getMessengerId(object)
        if id in self._id2object:
            record = self._id2object[id]
            record[0] -= 1
            if record[0] <= 0:
                del self._id2object[id]

    def accept(self, event, object, method, extraArgs=[], persistent=1):
        notifyDebug = Messenger.notify.getDebug()
        if notifyDebug:
            Messenger.notify.debug(
                'object: %s (%s)\n accepting: %s\n method: %s\n extraArgs: %s\n persistent: %s'
                % (safeRepr(object), self._getMessengerId(object), event,
                   safeRepr(method), safeRepr(extraArgs), persistent))
        if not (isinstance(extraArgs, list) or isinstance(extraArgs, tuple)
                or isinstance(extraArgs, set)):
            raise TypeError, 'A list is required as extraArgs argument'
        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.setdefault(event, {})
            id = self._getMessengerId(object)
            if id in acceptorDict:
                if notifyDebug:
                    oldMethod = acceptorDict[id][0]
                    if oldMethod == method:
                        self.notify.warning(
                            'object: %s was already accepting: "%s" with same callback: %s()'
                            % (object.__class__.__name__, safeRepr(event),
                               method.__name__))
                    else:
                        self.notify.warning(
                            'object: %s accept: "%s" new callback: %s() supplanting old callback: %s()'
                            % (object.__class__.__name__, safeRepr(event),
                               method.__name__, oldMethod.__name__))
            acceptorDict[id] = [method, extraArgs, persistent]
            eventDict = self.__objectEvents.setdefault(id, {})
            if event not in eventDict:
                self._storeObject(object)
                eventDict[event] = None
        finally:
            self.lock.release()

        return

    def ignore(self, event, object):
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring: ' %
                (self._getMessengerId(object), ) + safeRepr(event))
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            acceptorDict = self.__callbacks.get(event)
            if acceptorDict and id in acceptorDict:
                del acceptorDict[id]
                if len(acceptorDict) == 0:
                    del self.__callbacks[event]
            eventDict = self.__objectEvents.get(id)
            if eventDict and event in eventDict:
                del eventDict[event]
                if len(eventDict) == 0:
                    del self.__objectEvents[id]
                self._releaseObject(object)
        finally:
            self.lock.release()

    def ignoreAll(self, object):
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring all events' %
                (self._getMessengerId(object), ))
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                for event in eventDict.keys():
                    acceptorDict = self.__callbacks.get(event)
                    if acceptorDict and id in acceptorDict:
                        del acceptorDict[id]
                        if len(acceptorDict) == 0:
                            del self.__callbacks[event]
                    self._releaseObject(object)

                del self.__objectEvents[id]
        finally:
            self.lock.release()

    def getAllAccepting(self, object):
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                return eventDict.keys()
            return []
        finally:
            self.lock.release()

    def isAccepting(self, event, object):
        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.get(event)
            id = self._getMessengerId(object)
            if acceptorDict and id in acceptorDict:
                return 1
            return 0
        finally:
            self.lock.release()

    def whoAccepts(self, event):
        return self.__callbacks.get(event)

    def isIgnoring(self, event, object):
        return not self.isAccepting(event, object)

    def send(self, event, sentArgs=[], taskChain=None):
        if Messenger.notify.getDebug() and not self.quieting.get(event):
            pass
        self.lock.acquire()
        try:
            foundWatch = 0
            acceptorDict = self.__callbacks.get(event)
            if not acceptorDict:
                return
            if taskChain:
                from direct.task.TaskManagerGlobal import taskMgr
                queue = self._eventQueuesByTaskChain.setdefault(taskChain, [])
                queue.append((acceptorDict, event, sentArgs, foundWatch))
                if len(queue) == 1:
                    taskMgr.add(self.__taskChainDispatch,
                                name='Messenger-%s' % taskChain,
                                extraArgs=[taskChain],
                                taskChain=taskChain,
                                appendTask=True)
            else:
                self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
        finally:
            self.lock.release()

    def __taskChainDispatch(self, taskChain, task):
        while True:
            eventTuple = None
            self.lock.acquire()
            try:
                queue = self._eventQueuesByTaskChain.get(taskChain, None)
                if queue:
                    eventTuple = queue[0]
                    del queue[0]
                if not queue:
                    if queue is not None:
                        del self._eventQueuesByTaskChain[taskChain]
                if not eventTuple:
                    return task.done
                self.__dispatch(*eventTuple)
            finally:
                self.lock.release()

        return task.done

    def __dispatch(self, acceptorDict, event, sentArgs, foundWatch):
        for id in acceptorDict.keys():
            callInfo = acceptorDict.get(id)
            if callInfo:
                method, extraArgs, persistent = callInfo
                if not persistent:
                    eventDict = self.__objectEvents.get(id)
                    if eventDict and event in eventDict:
                        del eventDict[event]
                        if len(eventDict) == 0:
                            del self.__objectEvents[id]
                        self._releaseObject(self._getObject(id))
                    del acceptorDict[id]
                    if event in self.__callbacks and len(
                            self.__callbacks[event]) == 0:
                        del self.__callbacks[event]
                self.lock.release()
                try:
                    method(*(extraArgs + sentArgs))
                finally:
                    self.lock.acquire()

    def clear(self):
        self.lock.acquire()
        try:
            self.__callbacks.clear()
            self.__objectEvents.clear()
            self._id2object.clear()
        finally:
            self.lock.release()

    def isEmpty(self):
        return len(self.__callbacks) == 0

    def getEvents(self):
        return self.__callbacks.keys()

    def replaceMethod(self, oldMethod, newFunction):
        retFlag = 0
        for entry in self.__callbacks.items():
            event, objectDict = entry
            for objectEntry in objectDict.items():
                object, params = objectEntry
                method = params[0]
                if type(method) == types.MethodType:
                    function = method.im_func
                else:
                    function = method
                if function == oldMethod:
                    newMethod = types.MethodType(newFunction, method.im_self,
                                                 method.im_class)
                    params[0] = newMethod
                    retFlag += 1

        return retFlag

    def toggleVerbose(self):
        isVerbose = 1 - Messenger.notify.getDebug()
        Messenger.notify.setDebug(isVerbose)
        if isVerbose:
            print 'Verbose mode true.  quiet list = %s' % (
                self.quieting.keys(), )

    def find(self, needle):
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print self.__eventRepr(event),
                return {event: self.__callbacks[event]}

    def findAll(self, needle, limit=None):
        matches = {}
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print self.__eventRepr(event),
                matches[event] = self.__callbacks[event]
                if limit > 0:
                    limit -= 1
                    if limit == 0:
                        break

        return matches

    def __methodRepr(self, method):
        if type(method) == types.MethodType:
            functionName = method.im_class.__name__ + '.' + method.im_func.__name__
        else:
            if hasattr(method, '__name__'):
                functionName = method.__name__
            else:
                return ''
        return functionName

    def __eventRepr(self, event):
        str = event.ljust(32) + '\t'
        acceptorDict = self.__callbacks[event]
        for key, (method, extraArgs, persistent) in acceptorDict.items():
            str = str + self.__methodRepr(method) + ' '

        str = str + '\n'
        return str

    def __repr__(self):
        str = 'The messenger is currently handling:\n' + '=' * 64 + '\n'
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            str += self.__eventRepr(event)

        str += '=' * 64 + '\n'
        for key, eventDict in self.__objectEvents.items():
            object = self._getObject(key)
            str += '%s:\n' % repr(object)
            for event in eventDict.keys():
                str += '     %s\n' % repr(event)

        str += '=' * 64 + '\n' + 'End of messenger info.\n'
        return str

    def detailedRepr(self):
        import types
        str = 'Messenger\n'
        str = str + '=' * 50 + '\n'
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            acceptorDict = self.__callbacks[event]
            str = str + 'Event: ' + event + '\n'
            for key in acceptorDict.keys():
                function, extraArgs, persistent = acceptorDict[key]
                object = self._getObject(key)
                if type(object) == types.InstanceType:
                    className = object.__class__.__name__
                else:
                    className = 'Not a class'
                functionName = function.__name__
                str = str + '\t' + 'Acceptor:     ' + className + ' instance' + '\n\t' + 'Function name:' + functionName + '\n\t' + 'Extra Args:   ' + repr(
                    extraArgs) + '\n\t' + 'Persistent:   ' + repr(
                        persistent) + '\n'
                if type(function) == types.MethodType:
                    str = str + '\t' + 'Method:       ' + repr(
                        function) + '\n\t' + 'Function:     ' + repr(
                            function.im_func) + '\n'
                else:
                    str = str + '\t' + 'Function:     ' + repr(function) + '\n'

        str = str + '=' * 50 + '\n'
        return str
コード例 #5
0
class PackageInstaller(DirectObject):

    """ This class is used in a p3d runtime environment to manage the
    asynchronous download and installation of packages.  If you just
    want to install a package synchronously, see
    appRunner.installPackage() for a simpler interface.

    To use this class, you should subclass from it and override any of
    the six callback methods: downloadStarted(), packageStarted(),
    packageProgress(), downloadProgress(), packageFinished(),
    downloadFinished().

    Also see DWBPackageInstaller, which does exactly this, to add a
    DirectWaitBar GUI.

    """

    notify = directNotify.newCategory("PackageInstaller")

    globalLock = Lock()
    nextUniqueId = 1

    # This is a chain of state values progressing forward in time.
    S_initial = 0    # addPackage() calls are being made
    S_ready = 1      # donePackages() has been called
    S_started = 2    # download has started
    S_done = 3       # download is over

    class PendingPackage:
        """ This class describes a package added to the installer for
        download. """

        notify = directNotify.newCategory("PendingPackage")

        def __init__(self, packageName, version, host):
            self.packageName = packageName
            self.version = version
            self.host = host

            # This will be filled in properly by checkDescFile() or
            # getDescFile(); in the meantime, set a placeholder.
            self.package = PackageInfo(host, packageName, version)

            # Set true when the package has finished downloading,
            # either successfully or unsuccessfully.
            self.done = False

            # Set true or false when self.done has been set.
            self.success = False

            # Set true when the packageFinished() callback has been
            # delivered.
            self.notified = False

            # These are used to ensure the callbacks only get
            # delivered once for a particular package.
            self.calledPackageStarted = False
            self.calledPackageFinished = False

            # This is the amount of stuff we have to process to
            # install this package, and the amount of stuff we have
            # processed so far.  "Stuff" includes bytes downloaded,
            # bytes uncompressed, and bytes extracted; and each of
            # which is weighted differently into one grand total.  So,
            # the total doesn't really represent bytes; it's a
            # unitless number, which means something only as a ratio
            # to other packages.  Filled in by checkDescFile() or
            # getDescFile().
            self.downloadEffort = 0

            # Similar, but this is the theoretical effort if the
            # package were already downloaded.
            self.prevDownloadedEffort = 0

        def __cmp__(self, pp):
            """ Python comparision function.  This makes all
            PendingPackages withe same (packageName, version, host)
            combination be deemed equivalent. """
            return cmp((self.packageName, self.version, self.host),
                       (pp.packageName, pp.version, pp.host))

        def getProgress(self):
            """ Returns the download progress of this package in the
            range 0..1. """

            return self.package.downloadProgress

        def checkDescFile(self):
            """ Returns true if the desc file is already downloaded
            and good, or false if it needs to be downloaded. """

            if not self.host.hasCurrentContentsFile():
                # If the contents file isn't ready yet, we can't check
                # the desc file yet.
                return False

            # All right, get the package info now.
            package = self.host.getPackage(self.packageName, self.version)
            if not package:
                self.notify.warning("Package %s %s not known on %s" % (
                    self.packageName, self.version, self.host.hostUrl))
                return False

            self.package = package
            self.package.checkStatus()

            if not self.package.hasDescFile:
                return False

            self.downloadEffort = self.package.getDownloadEffort()
            self.prevDownloadEffort = 0
            if self.downloadEffort == 0:
                self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()

            return True


        def getDescFile(self, http):
            """ Synchronously downloads the desc files required for
            the package. """

            if not self.host.downloadContentsFile(http):
                return False

            # All right, get the package info now.
            package = self.host.getPackage(self.packageName, self.version)
            if not package:
                self.notify.warning("Package %s %s not known on %s" % (
                    self.packageName, self.version, self.host.hostUrl))
                return False

            self.package = package
            if not self.package.downloadDescFile(http):
                return False

            self.package.checkStatus()
            self.downloadEffort = self.package.getDownloadEffort()
            self.prevDownloadEffort = 0
            if self.downloadEffort == 0:
                self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()

            return True

    def __init__(self, appRunner, taskChain = 'default'):
        self.globalLock.acquire()
        try:
            self.uniqueId = PackageInstaller.nextUniqueId
            PackageInstaller.nextUniqueId += 1
        finally:
            self.globalLock.release()

        self.appRunner = appRunner
        self.taskChain = taskChain

        # If we're to be running on an asynchronous task chain, and
        # the task chain hasn't yet been set up already, create the
        # default parameters now.
        if taskChain != 'default' and not taskMgr.hasTaskChain(self.taskChain):
            taskMgr.setupTaskChain(self.taskChain, numThreads = 1,
                                   threadPriority = TPLow)

        self.callbackLock = Lock()
        self.calledDownloadStarted = False
        self.calledDownloadFinished = False

        # A list of all packages that have been added to the
        # installer.
        self.packageLock = RLock()
        self.packages = []
        self.state = self.S_initial

        # A list of packages that are waiting for their desc files.
        self.needsDescFile = []
        self.descFileTask = None

        # A list of packages that are waiting to be downloaded and
        # installed.
        self.needsDownload = []
        self.downloadTask = None

        # A list of packages that were already done at the time they
        # were passed to addPackage().
        self.earlyDone = []

        # A list of packages that have been successfully installed, or
        # packages that have failed.
        self.done = []
        self.failed = []

        # This task is spawned on the default task chain, to update
        # the status during the download.
        self.progressTask = None

        self.accept('PackageInstaller-%s-allHaveDesc' % self.uniqueId,
                    self.__allHaveDesc)
        self.accept('PackageInstaller-%s-packageStarted' % self.uniqueId,
                    self.__packageStarted)
        self.accept('PackageInstaller-%s-packageDone' % self.uniqueId,
                    self.__packageDone)

    def destroy(self):
        """ Interrupts all pending downloads.  No further callbacks
        will be made. """
        self.cleanup()

    def cleanup(self):
        """ Interrupts all pending downloads.  No further callbacks
        will be made. """

        self.packageLock.acquire()
        try:
            if self.descFileTask:
                taskMgr.remove(self.descFileTask)
                self.descFileTask = None
            if self.downloadTask:
                taskMgr.remove(self.downloadTask)
                self.downloadTask = None
        finally:
            self.packageLock.release()

        if self.progressTask:
            taskMgr.remove(self.progressTask)
            self.progressTask = None

        self.ignoreAll()

    def addPackage(self, packageName, version = None, hostUrl = None):
        """ Adds the named package to the list of packages to be
        downloaded.  Call donePackages() to finish the list. """

        if self.state != self.S_initial:
            raise ValueError, 'addPackage called after donePackages'

        host = self.appRunner.getHostWithAlt(hostUrl)
        pp = self.PendingPackage(packageName, version, host)

        self.packageLock.acquire()
        try:
            self.__internalAddPackage(pp)
        finally:
            self.packageLock.release()

    def __internalAddPackage(self, pp):
        """ Adds the indicated "pending package" to the appropriate
        list(s) for downloading and installing.  Assumes packageLock
        is already held."""

        if pp in self.packages:
            # Already added.
            return

        self.packages.append(pp)

        # We always add the package to needsDescFile, even if we
        # already have its desc file; this guarantees that packages
        # are downloaded in the order they are added.
        self.needsDescFile.append(pp)
        if not self.descFileTask:
            self.descFileTask = taskMgr.add(
                self.__getDescFileTask, 'getDescFile',
                taskChain = self.taskChain)

    def donePackages(self):
        """ After calling addPackage() for each package to be
        installed, call donePackages() to mark the end of the list.
        This is necessary to determine what the complete set of
        packages is (and therefore how large the total download size
        is).  None of the low-level callbacks will be made before this
        call. """

        if self.state != self.S_initial:
            # We've already been here.
            return

        # Throw the messages for packages that were already done
        # before we started.
        for pp in self.earlyDone:
            self.__donePackage(pp, True)
        self.earlyDone = []

        self.packageLock.acquire()
        try:
            if self.state != self.S_initial:
                return
            self.state = self.S_ready
            if not self.needsDescFile:
                # All package desc files are already available; so begin.
                self.__prepareToStart()
        finally:
            self.packageLock.release()

        if not self.packages:
            # Trivial no-op.
            self.__callDownloadFinished(True)

    def downloadStarted(self):
        """ This callback is made at some point after donePackages()
        is called; at the time of this callback, the total download
        size is known, and we can sensibly report progress through the
        whole. """

        self.notify.info("downloadStarted")

    def packageStarted(self, package):
        """ This callback is made for each package between
        downloadStarted() and downloadFinished() to indicate the start
        of a new package. """

        self.notify.debug("packageStarted: %s" % (package.packageName))

    def packageProgress(self, package, progress):
        """ This callback is made repeatedly between packageStarted()
        and packageFinished() to update the current progress on the
        indicated package only.  The progress value ranges from 0
        (beginning) to 1 (complete). """

        self.notify.debug("packageProgress: %s %s" % (package.packageName, progress))

    def downloadProgress(self, overallProgress):
        """ This callback is made repeatedly between downloadStarted()
        and downloadFinished() to update the current progress through
        all packages.  The progress value ranges from 0 (beginning) to
        1 (complete). """

        self.notify.debug("downloadProgress: %s" % (overallProgress))

    def packageFinished(self, package, success):
        """ This callback is made for each package between
        downloadStarted() and downloadFinished() to indicate that a
        package has finished downloading.  If success is true, there
        were no problems and the package is now installed.

        If this package did not require downloading (because it was
        already downloaded), this callback will be made immediately,
        *without* a corresponding call to packageStarted(), and may
        even be made before downloadStarted(). """

        self.notify.info("packageFinished: %s %s" % (package.packageName, success))

    def downloadFinished(self, success):
        """ This callback is made when all of the packages have been
        downloaded and installed (or there has been some failure).  If
        all packages where successfully installed, success is True.

        If there were no packages that required downloading, this
        callback will be made immediately, *without* a corresponding
        call to downloadStarted(). """

        self.notify.info("downloadFinished: %s" % (success))

    def __prepareToStart(self):
        """ This is called internally when transitioning from S_ready
        to S_started.  It sets up whatever initial values are
        needed.  Assumes self.packageLock is held.  Returns False if
        there were no packages to download, and the state was
        therefore transitioned immediately to S_done. """

        if not self.needsDownload:
            self.state = self.S_done
            return False

        self.state = self.S_started

        assert not self.downloadTask
        self.downloadTask = taskMgr.add(
            self.__downloadPackageTask, 'downloadPackage',
            taskChain = self.taskChain)

        assert not self.progressTask
        self.progressTask = taskMgr.add(
            self.__progressTask, 'packageProgress')

        return True

    def __allHaveDesc(self):
        """ This method is called internally when all of the pending
        packages have their desc info. """
        working = True

        self.packageLock.acquire()
        try:
            if self.state == self.S_ready:
                # We've already called donePackages(), so move on now.
                working = self.__prepareToStart()
        finally:
            self.packageLock.release()

        if not working:
            self.__callDownloadFinished(True)

    def __packageStarted(self, pp):
        """ This method is called when a single package is beginning
        to download. """

        self.__callDownloadStarted()
        self.__callPackageStarted(pp)

    def __packageDone(self, pp):
        """ This method is called when a single package has been
        downloaded and installed, or has failed. """

        self.__callPackageFinished(pp, pp.success)
        pp.notified = True

        # See if there are more packages to go.
        success = True
        allDone = True
        self.packageLock.acquire()
        try:
            for pp in self.packages:
                if pp.notified:
                    success = success and pp.success
                else:
                    allDone = False
        finally:
            self.packageLock.release()

        if allDone:
            self.__callDownloadFinished(success)

    def __callPackageStarted(self, pp):
        """ Calls the packageStarted() callback for a particular
        package if it has not already been called, being careful to
        avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not pp.calledPackageStarted:
                self.packageStarted(pp.package)
                self.packageProgress(pp.package, 0)
                pp.calledPackageStarted = True
        finally:
            self.callbackLock.release()

    def __callPackageFinished(self, pp, success):
        """ Calls the packageFinished() callback for a paricular
        package if it has not already been called, being careful to
        avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not pp.calledPackageFinished:
                if success:
                    self.packageProgress(pp.package, 1)
                self.packageFinished(pp.package, success)
                pp.calledPackageFinished = True
        finally:
            self.callbackLock.release()

    def __callDownloadStarted(self):
        """ Calls the downloadStarted() callback if it has not already
        been called, being careful to avoid race conditions. """

        self.callbackLock.acquire()
        try:
            if not self.calledDownloadStarted:
                self.downloadStarted()
                self.downloadProgress(0)
                self.calledDownloadStarted = True
        finally:
            self.callbackLock.release()

    def __callDownloadFinished(self, success):
        """ Calls the downloadFinished() callback if it has not
        already been called, being careful to avoid race
        conditions. """

        self.callbackLock.acquire()
        try:
            if not self.calledDownloadFinished:
                if success:
                    self.downloadProgress(1)
                self.downloadFinished(success)
                self.calledDownloadFinished = True
        finally:
            self.callbackLock.release()

    def __getDescFileTask(self, task):

        """ This task runs on the aysynchronous task chain; each pass,
        it extracts one package from self.needsDescFile and downloads
        its desc file.  On success, it adds the package to
        self.needsDownload. """

        self.packageLock.acquire()
        try:
            # If we've finished all of the packages that need desc
            # files, stop the task.
            if not self.needsDescFile:
                self.descFileTask = None

                eventName = 'PackageInstaller-%s-allHaveDesc' % self.uniqueId
                messenger.send(eventName, taskChain = 'default')

                return task.done
            pp = self.needsDescFile[0]
            del self.needsDescFile[0]
        finally:
            self.packageLock.release()

        # Now serve this one package.
        if not pp.checkDescFile():
            if not pp.getDescFile(self.appRunner.http):
                self.__donePackage(pp, False)
                return task.cont

        # This package is now ready to be downloaded.  We always add
        # it to needsDownload, even if it's already downloaded, to
        # guarantee ordering of packages.

        self.packageLock.acquire()
        try:
            # Also add any packages required by this one.
            for packageName, version, host in pp.package.requires:
                pp2 = self.PendingPackage(packageName, version, host)
                self.__internalAddPackage(pp2)
            self.needsDownload.append(pp)
        finally:
            self.packageLock.release()

        return task.cont

    def __downloadPackageTask(self, task):

        """ This task runs on the aysynchronous task chain; each pass,
        it extracts one package from self.needsDownload and downloads
        it. """

        while True:
            self.packageLock.acquire()
            try:
                # If we're done downloading, stop the task.
                if self.state == self.S_done or not self.needsDownload:
                    self.downloadTask = None
                    self.packageLock.release()
                    yield task.done; return

                assert self.state == self.S_started
                pp = self.needsDownload[0]
                del self.needsDownload[0]
            except:
                self.packageLock.release()
                raise
            self.packageLock.release()

            # Now serve this one package.
            eventName = 'PackageInstaller-%s-packageStarted' % self.uniqueId
            messenger.send(eventName, [pp], taskChain = 'default')

            if not pp.package.hasPackage:
                for token in pp.package.downloadPackageGenerator(self.appRunner.http):
                    if token == pp.package.stepContinue:
                        yield task.cont
                    else:
                        break

                if token != pp.package.stepComplete:
                    pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
                    pc.start()
                    self.__donePackage(pp, False)
                    pc.stop()
                    yield task.cont
                    continue

            # Successfully downloaded and installed.
            pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
            pc.start()
            self.__donePackage(pp, True)
            pc.stop()

            # Continue the loop without yielding, so we pick up the
            # next package within this same frame.

    def __donePackage(self, pp, success):
        """ Marks the indicated package as done, either successfully
        or otherwise. """
        assert not pp.done

        if success:
            pc = PStatCollector(':App:PackageInstaller:install:%s' % (pp.package.packageName))
            pc.start()
            pp.package.installPackage(self.appRunner)
            pc.stop()

        self.packageLock.acquire()
        try:
            pp.done = True
            pp.success = success
            if success:
                self.done.append(pp)
            else:
                self.failed.append(pp)
        finally:
            self.packageLock.release()

        eventName = 'PackageInstaller-%s-packageDone' % self.uniqueId
        messenger.send(eventName, [pp], taskChain = 'default')

    def __progressTask(self, task):
        self.callbackLock.acquire()
        try:
            if not self.calledDownloadStarted:
                # We haven't yet officially started the download.
                return task.cont

            if self.calledDownloadFinished:
                # We've officially ended the download.
                self.progressTask = None
                return task.done

            downloadEffort = 0
            currentDownloadSize = 0
            for pp in self.packages:
                downloadEffort += pp.downloadEffort + pp.prevDownloadedEffort
                packageProgress = pp.getProgress()
                currentDownloadSize += pp.downloadEffort * packageProgress + pp.prevDownloadedEffort
                if pp.calledPackageStarted and not pp.calledPackageFinished:
                    self.packageProgress(pp.package, packageProgress)

            if not downloadEffort:
                progress = 1
            else:
                progress = float(currentDownloadSize) / float(downloadEffort)
            self.downloadProgress(progress)

        finally:
            self.callbackLock.release()

        return task.cont
コード例 #6
0
ファイル: Messenger.py プロジェクト: CJT-Jackton/panda3d
class Messenger:

    notify = DirectNotifyGlobal.directNotify.newCategory("Messenger")

    def __init__(self):
        """
        One is keyed off the event name. It has the following structure:
            {event1: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]},
             event2: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]}}

        This dictionary allow for efficient callbacks when the messenger
        hears an event.

        A second dictionary remembers which objects are accepting which
        events. This allows for efficient ignoreAll commands.


        Or, for an example with more real data:
            {'mouseDown': {avatar: [avatar.jump, [2.0], 1]}}
        """
        # eventName->objMsgrId->callbackInfo
        self.__callbacks = {}
        # objMsgrId->set(eventName)
        self.__objectEvents = {}
        self._messengerIdGen = 0
        # objMsgrId->listenerObject
        self._id2object = {}

        # A mapping of taskChain -> eventList, used for sending events
        # across task chains (and therefore across threads).
        self._eventQueuesByTaskChain = {}

        # This protects the data structures within this object from
        # multithreaded access.
        self.lock = Lock()

        if __debug__:
            self.__isWatching=0
            self.__watching={}
        # I'd like this to be in the __debug__, but I fear that someone will
        # want this in a release build.  If you're sure that that will not be
        # then please remove this comment and put the quiet/verbose stuff
        # under __debug__.
        self.quieting={"NewFrame":1,
                       "avatarMoving":1,
                       "event-loop-done":1,
                       'collisionLoopFinished':1,
                       } # see def quiet()

    def _getMessengerId(self, object):
        # TODO: allocate this id in DirectObject.__init__ and get derived
        # classes to call down (speed optimization, assuming objects
        # accept/ignore more than once over their lifetime)
        # get unique messenger id for this object
        # assumes lock is held.
        if not hasattr(object, '_MSGRmessengerId'):
            object._MSGRmessengerId = (object.__class__.__name__, self._messengerIdGen)
            self._messengerIdGen += 1
        return object._MSGRmessengerId

    def _storeObject(self, object):
        # store reference-counted reference to object in case we need to
        # retrieve it later.  assumes lock is held.
        id = self._getMessengerId(object)
        if id not in self._id2object:
            self._id2object[id] = [1, object]
        else:
            self._id2object[id][0] += 1

    def _getObject(self, id):
        return self._id2object[id][1]

    def _getObjects(self):
        self.lock.acquire()
        try:
            objs = []
            for refCount, obj in self._id2object.values():
                objs.append(obj)
            return objs
        finally:
            self.lock.release()

    def _getNumListeners(self, event):
        return len(self.__callbacks.get(event, {}))

    def _getEvents(self):
        return list(self.__callbacks.keys())

    def _releaseObject(self, object):
        # assumes lock is held.
        id = self._getMessengerId(object)
        if id in self._id2object:
            record = self._id2object[id]
            record[0] -= 1
            if record[0] <= 0:
                del self._id2object[id]

    def accept(self, event, object, method, extraArgs=[], persistent=1):
        """ accept(self, string, DirectObject, Function, List, Boolean)

        Make this object accept this event. When the event is
        sent (using Messenger.send or from C++), method will be executed,
        optionally passing in extraArgs.

        If the persistent flag is set, it will continue to respond
        to this event, otherwise it will respond only once.
        """
        notifyDebug = Messenger.notify.getDebug()
        if notifyDebug:
            Messenger.notify.debug(
                "object: %s (%s)\n accepting: %s\n method: %s\n extraArgs: %s\n persistent: %s" %
                (safeRepr(object), self._getMessengerId(object), event, safeRepr(method),
                 safeRepr(extraArgs), persistent))

        # Make sure that the method is callable
        assert hasattr(method, '__call__'), (
            "method not callable in accept (ignoring): %s %s"%
            (safeRepr(method), safeRepr(extraArgs)))

        # Make sure extraArgs is a list or tuple
        if not (isinstance(extraArgs, list) or isinstance(extraArgs, tuple) or isinstance(extraArgs, set)):
            raise TypeError("A list is required as extraArgs argument")

        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.setdefault(event, {})

            id = self._getMessengerId(object)

            # Make sure we are not inadvertently overwriting an existing event
            # on this particular object.
            if id in acceptorDict:
                # TODO: we're replacing the existing callback. should this be an error?
                if notifyDebug:
                    oldMethod = acceptorDict[id][0]
                    if oldMethod == method:
                        self.notify.warning(
                            "object: %s was already accepting: \"%s\" with same callback: %s()" %
                            (object.__class__.__name__, safeRepr(event), method.__name__))
                    else:
                        self.notify.warning(
                            "object: %s accept: \"%s\" new callback: %s() supplanting old callback: %s()" %
                            (object.__class__.__name__, safeRepr(event), method.__name__, oldMethod.__name__))

            acceptorDict[id] = [method, extraArgs, persistent]

            # Remember that this object is listening for this event
            eventDict = self.__objectEvents.setdefault(id, {})
            if event not in eventDict:
                self._storeObject(object)
                eventDict[event] = None
        finally:
            self.lock.release()

    def ignore(self, event, object):
        """ ignore(self, string, DirectObject)
        Make this object no longer respond to this event.
        It is safe to call even if it was not already accepting
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring: ' % (self._getMessengerId(object), ) + safeRepr(event))

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Find the dictionary of all the objects accepting this event
            acceptorDict = self.__callbacks.get(event)
            # If this object is there, delete it from the dictionary
            if acceptorDict and id in acceptorDict:
                del acceptorDict[id]
                # If this dictionary is now empty, remove the event
                # entry from the Messenger alltogether
                if (len(acceptorDict) == 0):
                    del self.__callbacks[event]

            # This object is no longer listening for this event
            eventDict = self.__objectEvents.get(id)
            if eventDict and event in eventDict:
                del eventDict[event]
                if (len(eventDict) == 0):
                    del self.__objectEvents[id]

                self._releaseObject(object)
        finally:
            self.lock.release()

    def ignoreAll(self, object):
        """
        Make this object no longer respond to any events it was accepting
        Useful for cleanup
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug(
                safeRepr(object) + ' (%s)\n now ignoring all events' % (self._getMessengerId(object), ))

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                for event in list(eventDict.keys()):
                    # Find the dictionary of all the objects accepting this event
                    acceptorDict = self.__callbacks.get(event)
                    # If this object is there, delete it from the dictionary
                    if acceptorDict and id in acceptorDict:
                        del acceptorDict[id]
                        # If this dictionary is now empty, remove the event
                        # entry from the Messenger alltogether
                        if (len(acceptorDict) == 0):
                            del self.__callbacks[event]
                    self._releaseObject(object)
                del self.__objectEvents[id]
        finally:
            self.lock.release()

    def getAllAccepting(self, object):
        """
        Returns the list of all events accepted by the indicated object.
        """
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                return list(eventDict.keys())
            return []
        finally:
            self.lock.release()

    def isAccepting(self, event, object):
        """ isAccepting(self, string, DirectOject)
        Is this object accepting this event?
        """
        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.get(event)
            id = self._getMessengerId(object)
            if acceptorDict and id in acceptorDict:
                # Found it, return true
                return 1
            # If we looked in both dictionaries and made it here
            # that object must not be accepting that event.
            return 0
        finally:
            self.lock.release()

    def whoAccepts(self, event):
        """
        Return objects accepting the given event
        """
        return self.__callbacks.get(event)

    def isIgnoring(self, event, object):
        """ isIgnorning(self, string, DirectObject)
        Is this object ignoring this event?
        """
        return (not self.isAccepting(event, object))

    def send(self, event, sentArgs=[], taskChain = None):
        """
        Send this event, optionally passing in arguments

        event is usually a string.
        sentArgs is a list of any data that you want passed along to the
            handlers listening to this event.

        If taskChain is not None, it is the name of the task chain
        which should receive the event.  If taskChain is None, the
        event is handled immediately.  Setting a non-None taskChain
        will defer the event (possibly till next frame or even later)
        and create a new, temporary task within the named taskChain,
        but this is the only way to send an event across threads.
        """
        if Messenger.notify.getDebug() and not self.quieting.get(event):
            assert Messenger.notify.debug(
                'sent event: %s sentArgs = %s, taskChain = %s' % (
                event, sentArgs, taskChain))

        self.lock.acquire()
        try:
            foundWatch=0
            if __debug__:
                if self.__isWatching:
                    for i in self.__watching.keys():
                        if str(event).find(i) >= 0:
                            foundWatch=1
                            break
            acceptorDict = self.__callbacks.get(event)
            if not acceptorDict:
                if __debug__:
                    if foundWatch:
                        print("Messenger: \"%s\" was sent, but no function in Python listened."%(event,))
                return

            if taskChain:
                # Queue the event onto the indicated task chain.
                from direct.task.TaskManagerGlobal import taskMgr
                queue = self._eventQueuesByTaskChain.setdefault(taskChain, [])
                queue.append((acceptorDict, event, sentArgs, foundWatch))
                if len(queue) == 1:
                    # If this is the first (only) item on the queue,
                    # spawn the task to empty it.
                    taskMgr.add(self.__taskChainDispatch, name = 'Messenger-%s' % (taskChain),
                                extraArgs = [taskChain], taskChain = taskChain,
                                appendTask = True)
            else:
                # Handle the event immediately.
                self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
        finally:
            self.lock.release()

    def __taskChainDispatch(self, taskChain, task):
        """ This task is spawned each time an event is sent across
        task chains.  Its job is to empty the task events on the queue
        for this particular task chain.  This guarantees that events
        are still delivered in the same order they were sent. """

        while True:
            eventTuple = None
            self.lock.acquire()
            try:
                queue = self._eventQueuesByTaskChain.get(taskChain, None)
                if queue:
                    eventTuple = queue[0]
                    del queue[0]
                if not queue:
                    # The queue is empty, we're done.
                    if queue is not None:
                        del self._eventQueuesByTaskChain[taskChain]

                if not eventTuple:
                    # No event; we're done.
                    return task.done

                self.__dispatch(*eventTuple)
            finally:
                self.lock.release()

        return task.done

    def __dispatch(self, acceptorDict, event, sentArgs, foundWatch):
        for id in list(acceptorDict.keys()):
            # We have to make this apparently redundant check, because
            # it is possible that one object removes its own hooks
            # in response to a handler called by a previous object.
            #
            # NOTE: there is no danger of skipping over objects due to
            # modifications to acceptorDict, since the for..in above
            # iterates over a list of objects that is created once at
            # the start
            callInfo = acceptorDict.get(id)
            if callInfo:
                method, extraArgs, persistent = callInfo
                # If this object was only accepting this event once,
                # remove it from the dictionary
                if not persistent:
                    # This object is no longer listening for this event
                    eventDict = self.__objectEvents.get(id)
                    if eventDict and event in eventDict:
                        del eventDict[event]
                        if (len(eventDict) == 0):
                            del self.__objectEvents[id]
                        self._releaseObject(self._getObject(id))

                    del acceptorDict[id]
                    # If the dictionary at this event is now empty, remove
                    # the event entry from the Messenger altogether
                    if (event in self.__callbacks \
                            and (len(self.__callbacks[event]) == 0)):
                        del self.__callbacks[event]

                if __debug__:
                    if foundWatch:
                        print("Messenger: \"%s\" --> %s%s"%(
                            event,
                            self.__methodRepr(method),
                            tuple(extraArgs + sentArgs)))

                #print "Messenger: \"%s\" --> %s%s"%(
                #            event,
                #            self.__methodRepr(method),
                #            tuple(extraArgs + sentArgs))

                # It is important to make the actual call here, after
                # we have cleaned up the accept hook, because the
                # method itself might call accept() or acceptOnce()
                # again.
                assert hasattr(method, '__call__')

                # Release the lock temporarily while we call the method.
                self.lock.release()
                try:
                    method (*(extraArgs + sentArgs))
                finally:
                    self.lock.acquire()

    def clear(self):
        """
        Start fresh with a clear dict
        """
        self.lock.acquire()
        try:
            self.__callbacks.clear()
            self.__objectEvents.clear()
            self._id2object.clear()
        finally:
            self.lock.release()

    def isEmpty(self):
        return (len(self.__callbacks) == 0)

    def getEvents(self):
        return list(self.__callbacks.keys())

    def replaceMethod(self, oldMethod, newFunction):
        """
        This is only used by Finder.py - the module that lets
        you redefine functions with Control-c-Control-v
        """
        retFlag = 0
        for entry in list(self.__callbacks.items()):
            event, objectDict = entry
            for objectEntry in list(objectDict.items()):
                object, params = objectEntry
                method = params[0]
                if (type(method) == types.MethodType):
                    function = method.__func__
                else:
                    function = method
                #print ('function: ' + repr(function) + '\n' +
                #       'method: ' + repr(method) + '\n' +
                #       'oldMethod: ' + repr(oldMethod) + '\n' +
                #       'newFunction: ' + repr(newFunction) + '\n')
                if (function == oldMethod):
                    newMethod = types.MethodType(
                        newFunction, method.__self__, method.__self__.__class__)
                    params[0] = newMethod
                    # Found it retrun true
                    retFlag += 1
        # didn't find that method, return false
        return retFlag

    def toggleVerbose(self):
        isVerbose = 1 - Messenger.notify.getDebug()
        Messenger.notify.setDebug(isVerbose)
        if isVerbose:
            print("Verbose mode true.  quiet list = %s"%(
                list(self.quieting.keys()),))

    if __debug__:
        def watch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unwatch
            """
            if not self.__watching.get(needle):
                self.__isWatching += 1
                self.__watching[needle]=1

        def unwatch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: watch
            """
            if self.__watching.get(needle):
                self.__isWatching -= 1
                del self.__watching[needle]

        def quiet(self, message):
            """
            When verbose mode is on, don't spam the output with messages
            marked as quiet.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unquiet
            """
            if not self.quieting.get(message):
                self.quieting[message]=1

        def unquiet(self, message):
            """
            Remove a message from the list of messages that are not reported
            in verbose mode.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: quiet
            """
            if self.quieting.get(message):
                del self.quieting[message]

    def find(self, needle):
        """
        return a matching event (needle) if found (in haystack).
        This is primarily a debugging tool.
        """
        keys = list(self.__callbacks.keys())
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print(self.__eventRepr(event))
                return {event: self.__callbacks[event]}

    def findAll(self, needle, limit=None):
        """
        return a dict of events (needle) if found (in haystack).
        limit may be None or an integer (e.g. 1).
        This is primarily a debugging tool.
        """
        matches = {}
        keys = list(self.__callbacks.keys())
        keys.sort()
        for event in keys:
            if repr(event).find(needle) >= 0:
                print(self.__eventRepr(event))
                matches[event] = self.__callbacks[event]
                # if the limit is not None, decrement and
                # check for break:
                if limit > 0:
                    limit -= 1
                    if limit == 0:
                        break
        return matches

    def __methodRepr(self, method):
        """
        return string version of class.method or method.
        """
        if (type(method) == types.MethodType):
            functionName = method.__self__.__class__.__name__ + '.' + \
                method.__func__.__name__
        else:
            if hasattr(method, "__name__"):
                functionName = method.__name__
            else:
                return ""
        return functionName

    def __eventRepr(self, event):
        """
        Compact version of event, acceptor pairs
        """
        str = event.ljust(32) + '\t'
        acceptorDict = self.__callbacks[event]
        for key, (method, extraArgs, persistent) in list(acceptorDict.items()):
            str = str + self.__methodRepr(method) + ' '
        str = str + '\n'
        return str

    def __repr__(self):
        """
        Compact version of event, acceptor pairs
        """
        str = "The messenger is currently handling:\n" + "="*64 + "\n"
        keys = list(self.__callbacks.keys())
        keys.sort()
        for event in keys:
            str += self.__eventRepr(event)
        # Print out the object: event dictionary too
        str += "="*64 + "\n"
        for key, eventDict in list(self.__objectEvents.items()):
            object = self._getObject(key)
            str += "%s:\n" % repr(object)
            for event in list(eventDict.keys()):
                str += "     %s\n" % repr(event)

        str += "="*64 + "\n" + "End of messenger info.\n"
        return str

    def detailedRepr(self):
        """
        Print out the table in a detailed readable format
        """
        import types
        str = 'Messenger\n'
        str = str + '='*50 + '\n'
        keys = list(self.__callbacks.keys())
        keys.sort()
        for event in keys:
            acceptorDict = self.__callbacks[event]
            str = str + 'Event: ' + event + '\n'
            for key in list(acceptorDict.keys()):
                function, extraArgs, persistent = acceptorDict[key]
                object = self._getObject(key)
                if (type(object) == types.InstanceType):
                    className = object.__class__.__name__
                else:
                    className = "Not a class"
                functionName = function.__name__
                str = (str + '\t' +
                       'Acceptor:     ' + className + ' instance' + '\n\t' +
                       'Function name:' + functionName + '\n\t' +
                       'Extra Args:   ' + repr(extraArgs) + '\n\t' +
                       'Persistent:   ' + repr(persistent) + '\n')
                # If this is a class method, get its actual function
                if (type(function) == types.MethodType):
                    str = (str + '\t' +
                           'Method:       ' + repr(function) + '\n\t' +
                           'Function:     ' + repr(function.__func__) + '\n')
                else:
                    str = (str + '\t' +
                           'Function:     ' + repr(function) + '\n')
        str = str + '='*50 + '\n'
        return str
コード例 #7
0
        # Add on the rest of the models for the last thread
        if i == numThreads - 1 and len(
                orig_models) > firstModel + modelsPerThread:
            models += orig_models[firstModel + modelsPerThread:]

        print len(models), "models on thread", i
        t = Thread(target=__threadRevamp, args=(models, ))
        threads.append(t)

    for t in threads:
        t.start()

    while True:
        prog_lock.acquire()
        prog = int(__progress)
        prog_lock.release()
        sys.stdout.write("Progress:\t{0}\t/\t{1}\r".format(
            prog, len(orig_models)))
        sys.stdout.flush()
        alive = 0
        for t in threads:
            if t.isAlive():
                alive += 1
        if alive == 0:
            break
else:
    print "Running without threads"
    __threadRevamp(orig_models)

if len(errors) > 0:
    print "Errors whilst revamping:"
コード例 #8
0
ファイル: main_menu.py プロジェクト: PlumpMath/Sogal
class MainMenu(SogalForm):
    '''
    main menu class
    '''
    def __init__(self, entry='ifselectionjumptest'):
        '''
        Constructor
        '''
        self.entry = entry
        self.closed = True

        SogalForm.__init__(self,
                           fading=True,
                           fading_duration=1.0,
                           backgroundImage=None,
                           backgroundColor=(0, 0, 0, 1),
                           enableMask=True,
                           hiddenFunc=self.closedFunc,
                           shownFunc=self.openedFunc)

        self.addButtonBar()

        self.fadinglock = Lock(
        )  #This lock is to prevent open or close failure when the main menu is on fading

    def addButtonBar(self):
        self.bar = VLayout(margin=0.1)
        self.bar.reparentTo(self)

    def addButtons(self):
        '''
        override this if you want custom buttons
        '''
        #TODO:Continue Button 注意继续的位置可能是从硬盘上读入的也可能是从游戏中返回标题画面的 也有可能是在游戏正常结束之后所以没有Continue数据
        self.addButton(text='New Game',
                       state=DGG.NORMAL,
                       command=self._startGame)
        self.addButton(text='Load', state=DGG.NORMAL, command=self._load)
        self.addButton(text='Options', state=DGG.NORMAL, command=self._config)
        #TODO:Gallery Button
        self.addButton(text='Exit', state=DGG.NORMAL, command=self._exit)

    def close(self):
        '''Called by SogalBase. Do something and hide, you will need it if you want a more complex main menu'''
        self.fadinglock.acquire()

        if self.closed:
            return
        SogalForm.hide(self)
        self.closed = True
        if self.bar:
            for btn in self.bar:
                btn['state'] = DGG.DISABLED

    def open(self):
        '''Called by SogalBase. Do something and show, you will need it if you want a more complex main menu'''
        self.fadinglock.acquire()

        if not self.closed:
            return
        SogalForm.show(self)
        self.addButtons()
        self.closed = False

    def show(self):
        #redirect show/hide to open/close
        self.open()

    def hide(self):
        #redirect show/hide to open/close
        self.close()

    def closedFunc(self):
        self.cleanup()
        self.fadinglock.release()

    def cleanup(self):
        for btn in self.bar:
            btn.destroy()
        self.bar.removeNode()
        self.addButtonBar()

    def openedFunc(self):
        self.fadinglock.release()

    def addButton(self, **args):
        '''Add a button and return it'''
        btn = DirectButton(**dict(base.getStyle()['mainMenuButton'],
                                  frameSize=BUTTON_SIZE,
                                  **args))  # @UndefinedVariable

        self.bar.append(btn)
        #self.vbox.pack(btn)
        return btn

    def _startGame(self, scene=None):
        if self.closed:
            return
        if not scene:
            messenger.send('start_game', [self.entry])
        else:
            messenger.send('start_game', [scene])

    def _load(self):
        if self.closed:
            return
        messenger.send('load_game')

    def _exit(self):
        if self.closed:
            return
        messenger.send('exit_game')

    def _config(self):
        messenger.send('config_form')
コード例 #9
0
ファイル: Messenger.py プロジェクト: nate97/RobotToonManager
class Messenger:

    notify = DirectNotifyGlobal.directNotify.newCategory("Messenger")

    def __init__(self):
        """
        One is keyed off the event name. It has the following structure:
            {event1: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]},
             event2: {object1: [method, extraArgs, persistent],
                       object2: [method, extraArgs, persistent]}}

        This dictionary allow for efficient callbacks when the messenger
        hears an event.

        A second dictionary remembers which objects are accepting which
        events. This allows for efficient ignoreAll commands.


        Or, for an example with more real data:
            {'mouseDown': {avatar: [avatar.jump, [2.0], 1]}}
        """
        self.__callbacks = {}
        self.__objectEvents = {}
        self._messengerIdGen = 0
        self._id2object = {}

        # A mapping of taskChain -> eventList, used for sending events
        # across task chains (and therefore across threads).
        self._eventQueuesByTaskChain = {}

        # This protects the data structures within this object from
        # multithreaded access.
        self.lock = Lock()

        if __debug__:
            self.__isWatching = 0
            self.__watching = {}
        # I'd like this to be in the __debug__, but I fear that someone will
        # want this in a release build.  If you're sure that that will not be
        # then please remove this comment and put the quiet/verbose stuff
        # under __debug__.
        self.quieting = {
            "NewFrame": 1,
            "avatarMoving": 1,
            "event-loop-done": 1,
        }  # see def quiet()

    def _getMessengerId(self, object):
        # TODO: allocate this id in DirectObject.__init__ and get derived
        # classes to call down (speed optimization, assuming objects
        # accept/ignore more than once over their lifetime)
        # get unique messenger id for this object
        # assumes lock is held.
        if not hasattr(object, '_messengerId'):
            object._messengerId = (object.__class__.__name__,
                                   self._messengerIdGen)
            self._messengerIdGen += 1
        return object._messengerId

    def _storeObject(self, object):
        # store reference-counted reference to object in case we need to
        # retrieve it later.  assumes lock is held.
        id = self._getMessengerId(object)
        if id not in self._id2object:
            self._id2object[id] = [1, object]
        else:
            self._id2object[id][0] += 1

    def _getObject(self, id):
        return self._id2object[id][1]

    def _getObjects(self):
        self.lock.acquire()
        try:
            objs = []
            for refCount, obj in self._id2object.itervalues():
                objs.append(obj)
            return objs
        finally:
            self.lock.release()

    def _getNumListeners(self, event):
        return len(self.__callbacks.get(event, {}))

    def _getEvents(self):
        return self.__callbacks.keys()

    def _releaseObject(self, object):
        # assumes lock is held.
        id = self._getMessengerId(object)
        if id in self._id2object:
            record = self._id2object[id]
            record[0] -= 1
            if record[0] <= 0:
                del self._id2object[id]

    def accept(self, event, object, method, extraArgs=[], persistent=1):
        """ accept(self, string, DirectObject, Function, List, Boolean)

        Make this object accept this event. When the event is
        sent (using Messenger.send or from C++), method will be executed,
        optionally passing in extraArgs.

        If the persistent flag is set, it will continue to respond
        to this event, otherwise it will respond only once.
        """
        notifyDebug = Messenger.notify.getDebug()
        if notifyDebug:
            Messenger.notify.debug(
                "object: %s\n accepting: %s\n method: %s\n extraArgs: %s\n persistent: %s"
                % (object, event, method, extraArgs, persistent))

        # Make the the method is callable
        assert callable(method), (
            "method not callable in accept (ignoring): %s %s" %
            (method, extraArgs))

        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.setdefault(event, {})

            id = self._getMessengerId(object)

            # Make sure we are not inadvertently overwriting an existing event
            # on this particular object.
            if notifyDebug:
                if acceptorDict.has_key(id):
                    oldMethod = acceptorDict[id][0]
                    if oldMethod == method:
                        self.notify.warning(
                            "object: %s was already accepting: \"%s\" with same callback: %s()"
                            % (object.__class__.__name__, event,
                               method.__name__))
                    else:
                        self.notify.warning(
                            "object: %s accept: \"%s\" new callback: %s() supplanting old callback: %s()"
                            % (object.__class__.__name__, event,
                               method.__name__, oldMethod.__name__))

            acceptorDict[id] = [method, extraArgs, persistent]

            # Remember that this object is listening for this event
            eventDict = self.__objectEvents.setdefault(id, {})
            eventDict.setdefault(event, None)
            self._storeObject(object)
        finally:
            self.lock.release()

    def ignore(self, event, object):
        """ ignore(self, string, DirectObject)
        Make this object no longer respond to this event.
        It is safe to call even if it was not already accepting
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug( ` object ` + '\n now ignoring: ' +
                                    ` event `)

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Find the dictionary of all the objects accepting this event
            acceptorDict = self.__callbacks.get(event)
            # If this object is there, delete it from the dictionary
            if acceptorDict and acceptorDict.has_key(id):
                del acceptorDict[id]
                # If this dictionary is now empty, remove the event
                # entry from the Messenger alltogether
                if (len(acceptorDict) == 0):
                    del self.__callbacks[event]

            # This object is no longer listening for this event
            eventDict = self.__objectEvents.get(id)
            if eventDict and eventDict.has_key(event):
                del eventDict[event]
                if (len(eventDict) == 0):
                    del self.__objectEvents[id]

            self._releaseObject(object)
        finally:
            self.lock.release()

    def ignoreAll(self, object):
        """
        Make this object no longer respond to any events it was accepting
        Useful for cleanup
        """
        if Messenger.notify.getDebug():
            Messenger.notify.debug( ` object ` + '\n now ignoring all events')

        self.lock.acquire()
        try:
            id = self._getMessengerId(object)
            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                for event in eventDict.keys():
                    # Find the dictionary of all the objects accepting this event
                    acceptorDict = self.__callbacks.get(event)
                    # If this object is there, delete it from the dictionary
                    if acceptorDict and acceptorDict.has_key(id):
                        del acceptorDict[id]
                        # If this dictionary is now empty, remove the event
                        # entry from the Messenger alltogether
                        if (len(acceptorDict) == 0):
                            del self.__callbacks[event]
                del self.__objectEvents[id]
            if id in self._id2object:
                del self._id2object[id]
        finally:
            self.lock.release()

    def getAllAccepting(self, object):
        """
        Returns the list of all events accepted by the indicated object.
        """
        self.lock.acquire()
        try:
            id = self._getMessengerId(object)

            # Get the list of events this object is listening to
            eventDict = self.__objectEvents.get(id)
            if eventDict:
                return eventDict.keys()
            return []
        finally:
            self.lock.release()

    def isAccepting(self, event, object):
        """ isAccepting(self, string, DirectOject)
        Is this object accepting this event?
        """
        self.lock.acquire()
        try:
            acceptorDict = self.__callbacks.get(event)
            id = self._getMessengerId(object)
            if acceptorDict and acceptorDict.has_key(id):
                # Found it, return true
                return 1
            # If we looked in both dictionaries and made it here
            # that object must not be accepting that event.
            return 0
        finally:
            self.lock.release()

    def whoAccepts(self, event):
        """
        Return objects accepting the given event
        """
        return self.__callbacks.get(event)

    def isIgnoring(self, event, object):
        """ isIgnorning(self, string, DirectObject)
        Is this object ignoring this event?
        """
        return (not self.isAccepting(event, object))

    def send(self, event, sentArgs=[], taskChain=None):
        """
        Send this event, optionally passing in arguments

        event is usually a string.
        sentArgs is a list of any data that you want passed along to the
            handlers listening to this event.

        If taskChain is not None, it is the name of the task chain
        which should receive the event.  If taskChain is None, the
        event is handled immediately.  Setting a non-None taskChain
        will defer the event (possibly till next frame or even later)
        and create a new, temporary task within the named taskChain,
        but this is the only way to send an event across threads.
        """
        if Messenger.notify.getDebug() and not self.quieting.get(event):
            assert Messenger.notify.debug(
                'sent event: %s sentArgs = %s, taskChain = %s' %
                (event, sentArgs, taskChain))

        self.lock.acquire()
        try:
            foundWatch = 0
            if __debug__:
                if self.__isWatching:
                    for i in self.__watching.keys():
                        if str(event).find(i) >= 0:
                            foundWatch = 1
                            break
            acceptorDict = self.__callbacks.get(event)
            if not acceptorDict:
                if __debug__:
                    if foundWatch:
                        print "Messenger: \"%s\" was sent, but no function in Python listened." % (
                            event, )
                return

            if taskChain:
                # Queue the event onto the indicated task chain.
                taskMgr.add(
                    self.__lockAndDispatch,
                    name='Messenger-%s-%s' % (event, taskChain),
                    extraArgs=[acceptorDict, event, sentArgs, foundWatch],
                    taskChain=taskChain)
            else:
                # Handle the event immediately.
                self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
        finally:
            self.lock.release()

    def __lockAndDispatch(self, acceptorDict, event, sentArgs, foundWatch):
        self.lock.acquire()
        try:
            self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
        finally:
            self.lock.release()

    def __dispatch(self, acceptorDict, event, sentArgs, foundWatch):
        for id in acceptorDict.keys():
            # We have to make this apparently redundant check, because
            # it is possible that one object removes its own hooks
            # in response to a handler called by a previous object.
            #
            # NOTE: there is no danger of skipping over objects due to
            # modifications to acceptorDict, since the for..in above
            # iterates over a list of objects that is created once at
            # the start
            callInfo = acceptorDict.get(id)
            if callInfo:
                method, extraArgs, persistent = callInfo
                # If this object was only accepting this event once,
                # remove it from the dictionary
                if not persistent:
                    # This object is no longer listening for this event
                    eventDict = self.__objectEvents.get(id)
                    if eventDict and eventDict.has_key(event):
                        del eventDict[event]
                        if (len(eventDict) == 0):
                            del self.__objectEvents[id]

                    del acceptorDict[id]
                    # If the dictionary at this event is now empty, remove
                    # the event entry from the Messenger altogether
                    if (self.__callbacks.has_key(event) \
                            and (len(self.__callbacks[event]) == 0)):
                        del self.__callbacks[event]

                if __debug__:
                    if foundWatch:
                        print "Messenger: \"%s\" --> %s%s" % (
                            event, self.__methodRepr(method),
                            tuple(extraArgs + sentArgs))

                #print "Messenger: \"%s\" --> %s%s"%(
                #            event,
                #            self.__methodRepr(method),
                #            tuple(extraArgs + sentArgs))

                # It is important to make the actual call here, after
                # we have cleaned up the accept hook, because the
                # method itself might call accept() or acceptOnce()
                # again.
                assert callable(method)

                # Release the lock temporarily while we call the method.
                self.lock.release()
                try:
                    method(*(extraArgs + sentArgs))
                finally:
                    self.lock.acquire()

    def clear(self):
        """
        Start fresh with a clear dict
        """
        self.lock.acquire()
        try:
            self.__callbacks.clear()
            self.__objectEvents.clear()
        finally:
            self.lock.release()

    def isEmpty(self):
        return (len(self.__callbacks) == 0)

    def getEvents(self):
        return self.__callbacks.keys()

    def replaceMethod(self, oldMethod, newFunction):
        """
        This is only used by Finder.py - the module that lets
        you redefine functions with Control-c-Control-v
        """
        import new
        retFlag = 0
        for entry in self.__callbacks.items():
            event, objectDict = entry
            for objectEntry in objectDict.items():
                object, params = objectEntry
                method = params[0]
                if (type(method) == types.MethodType):
                    function = method.im_func
                else:
                    function = method
                #print ('function: ' + `function` + '\n' +
                #       'method: ' + `method` + '\n' +
                #       'oldMethod: ' + `oldMethod` + '\n' +
                #       'newFunction: ' + `newFunction` + '\n')
                if (function == oldMethod):
                    newMethod = new.instancemethod(newFunction, method.im_self,
                                                   method.im_class)
                    params[0] = newMethod
                    # Found it retrun true
                    retFlag += 1
        # didn't find that method, return false
        return retFlag

    def toggleVerbose(self):
        isVerbose = 1 - Messenger.notify.getDebug()
        Messenger.notify.setDebug(isVerbose)
        if isVerbose:
            print "Verbose mode true.  quiet list = %s" % (
                self.quieting.keys(), )

    if __debug__:

        def watch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unwatch
            """
            if not self.__watching.get(needle):
                self.__isWatching += 1
                self.__watching[needle] = 1

        def unwatch(self, needle):
            """
            return a matching event (needle) if found (in haystack).
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: watch
            """
            if self.__watching.get(needle):
                self.__isWatching -= 1
                del self.__watching[needle]

        def quiet(self, message):
            """
            When verbose mode is on, don't spam the output with messages
            marked as quiet.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: unquiet
            """
            if not self.quieting.get(message):
                self.quieting[message] = 1

        def unquiet(self, message):
            """
            Remove a message from the list of messages that are not reported
            in verbose mode.
            This is primarily a debugging tool.

            This is intended for debugging use only.
            This function is not defined if python is ran with -O (optimize).

            See Also: quiet
            """
            if self.quieting.get(message):
                del self.quieting[message]

    def find(self, needle):
        """
        return a matching event (needle) if found (in haystack).
        This is primarily a debugging tool.
        """
        keys = self.__callbacks.keys()
        keys.sort()
        for event in keys:
            if ` event `.find(needle) >= 0:
                print self.__eventRepr(event),
                return {event: self.__callbacks[event]}
コード例 #10
0
ファイル: main_menu.py プロジェクト: WindyDarian/Sogal
class MainMenu(SogalForm):
    '''
    main menu class
    '''



    
    
    def __init__(self, entry = 'ifselectionjumptest'):
        '''
        Constructor
        '''
        self.entry = entry
        self.closed = True
        
        SogalForm.__init__(self, fading = True, fading_duration = 1.0, backgroundImage = None, backgroundColor = (0,0,0,1), 
                           enableMask = True,
                           hiddenFunc = self.closedFunc, shownFunc = self.openedFunc)
        

        self.addButtonBar()
        
        self.fadinglock = Lock()        #This lock is to prevent open or close failure when the main menu is on fading

        
    def addButtonBar(self):
        self.bar = VLayout(margin= 0.1)
        self.bar.reparentTo(self)        
        


    
    
    def addButtons(self):
        '''
        override this if you want custom buttons
        '''
        #TODO:Continue Button 注意继续的位置可能是从硬盘上读入的也可能是从游戏中返回标题画面的 也有可能是在游戏正常结束之后所以没有Continue数据
        self.addButton(text = 'New Game', state = DGG.NORMAL, command = self._startGame)
        self.addButton(text = 'Load', state = DGG.NORMAL, command = self._load)
        self.addButton(text = 'Options', state = DGG.NORMAL, command = self._config)
        #TODO:Gallery Button
        self.addButton(text = 'Exit', state = DGG.NORMAL, command = self._exit)       

            
    def close(self):
        '''Called by SogalBase. Do something and hide, you will need it if you want a more complex main menu'''
        self.fadinglock.acquire()
        
        if self.closed:
            return
        SogalForm.hide(self)
        self.closed = True
        if self.bar:
            for btn in self.bar:
                btn['state'] = DGG.DISABLED
        
    def open(self):
        '''Called by SogalBase. Do something and show, you will need it if you want a more complex main menu'''
        self.fadinglock.acquire()
        
        if not self.closed:
            return
        SogalForm.show(self)
        self.addButtons()
        self.closed = False
    
    def show(self):
        #redirect show/hide to open/close
        self.open()
    
    def hide(self):
        #redirect show/hide to open/close
        self.close()
        
    def closedFunc(self):
        self.cleanup()
        self.fadinglock.release()
    
    def cleanup(self):
        for btn in self.bar:
            btn.destroy()
        self.bar.removeNode()
        self.addButtonBar()
    
    def openedFunc(self):
        self.fadinglock.release()
        
    def addButton(self,**args):
        '''Add a button and return it'''
        btn = DirectButton(**dict(base.getStyle()['mainMenuButton'], frameSize = BUTTON_SIZE,**args))  # @UndefinedVariable
        
        self.bar.append(btn)
        #self.vbox.pack(btn)
        return btn
        
    def _startGame(self, scene = None):
        if self.closed:
            return
        if not scene:
            messenger.send('start_game', [self.entry])
        else:
            messenger.send('start_game', [scene])
            
    def _load(self):
        if self.closed:
            return
        messenger.send('load_game')
        
    def _exit(self):
        if self.closed:
            return
        messenger.send('exit_game')
        
    def _config(self):
        messenger.send('config_form')
コード例 #11
0
ファイル: sogal_text.py プロジェクト: PlumpMath/Sogal
class SogalText(NodePath):
    '''
    A text label, contains many TextLines
    '''
    def __init__(self,
                 parent = None,
                 pos = (0,0,0),
                 text = u'',
                 wordwrap = None, 
                 maxRows = None, 
                 spacing = 0,
                 lineSpacing = 0,
                 minLineHeight = 0.5,
                 font = None,
                 fg = (1,1,1,1),
                 scale = 0.07, 
                 shadow = None,
                 shadowOffset = (0.04, 0.04),
                 textScale = None,
                 ):
        '''
        Constructor
        :param parent: parent NodePath
        :param text: text
        :param font: font of the text
        :param wordwrap: set the width when wraping the word (note that )
        :param maxRows: max row of the text
        :param spacing: spacing of words
        :param lineSpacing: spacing of lines
        :param minLineHeight: height of a line when it is empty
        :param fg: foreground color
        :param scale: scale of the text
        :param shadow: shadow color of the text
        :param shadowOffset: shadow offset of the text
        '''
        self.destroyed = False
        self.__parent = parent or aspect2d
        self.__lerpLock = Lock()
        self.__font = font
        self.__currentLerpInterval = None
        self.wordwrap = wordwrap
        self.lines = []
        self.spacing = spacing
        self.lineSpacing = lineSpacing
        self.minLineHeight = minLineHeight
        
        self.maxRows = maxRows
        
        self.recordedText = [] #recorder text sections used in backup
        
        NodePath.__init__(self,'')
        self.setScale(scale)
        self.setPos(pos)
        
        self.currentHeight = 0
        
        
        self.reparentTo(self.__parent)  # @UndefinedVariable
        
        self.textMaker = TextNode('textMaker')
        if font:
            self.setFont(font, specNode = None)
        if fg:
            self.setFg(fg, specNode = None)
        if shadow:
            self.setShadow(shadow, shadowOffset, specNode = None)
        if textScale:
            self.setTexScale(textScale,specNode = None)
        
        self.textMaker.setAlign(TextNode.ALeft)
        
        if shadow:
            pass
        if text:
            self.appendText(text)
            
    def destroy(self):
        if self.__currentLerpInterval:
            self.__currentLerpInterval.pause()
        self.clear()
        if not self.destroyed:
            self.destroyed = True
        self.textMaker = None
        self.recordedText = None
        self.removeNode()
        
    def clear(self):
        if self.__currentLerpInterval:
            self.__currentLerpInterval.pause()
        self.currentHeight = 0
        for tl in self.lines:
            tl.removeNode()
        self.lines = []
        self.recordedText = []
    
    def setFg(self, fg, specNode = None):
        node = specNode or self.textMaker
        node.setTextColor(fg[0], fg[1], fg[2], fg[3])
        
    def setFont(self,font, specNode = None):
        node = specNode or self.textMaker
        node.setFont(font)
        
    def setShadow(self, shadow, offset = (0.04, 0.04), specNode = None):
        node = specNode or self.textMaker

        if shadow[3] != 0:
            node.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3])
            node.setShadow(offset)
        else:
            node.clearShadow()
            
    def setTextScale(self, scale , specNode = None):
        node = specNode or self.textMaker
        node.setTextScale(scale)
        
    def setMaxRows(self,maxrows):
        self.maxRows = maxrows
    
    def setWordwrap(self,wordwrap):
        self.wordwrap = wordwrap
        
    def setMinLineHeight(self,minLineHeight):
        self.minLineHeight = minLineHeight
        
    
    
    def appendText(self, text,speed = 0, fadein = 0, fadeinType = 0, newLine = False,
                   custom = False, font = None, textScale = 1, fg = (1,1,1,1), 
                   shadow = None, shadowOffset = (0.04, 0.04), **kwargs):
        textprops = dict(text = text,newLine = newLine, custom = custom, font = font, textScale = textScale, fg = fg, 
                 shadow = shadow, shadowOffset = shadowOffset, **kwargs)
        
        self.recordedText.append(textprops)
        
        self.appendStoredText(textprops, speed, fadein, fadeinType)
            

            
    def appendStoredText(self,textprops, speed = 0, fadein = 0, fadeinType = 0):
        #append a text stored with appendText() or by loading self.recordedText
        text = textprops['text']
        newLine = textprops['newLine']
        custom = textprops['custom']
        if custom:
            textMaker = TextNode('temptextmaker', self.textMaker)
            font = textprops['font']
            if font:
                textMaker.setFont(font)
            textScale = textprops['textScale']
            if textScale:
                textMaker.setTextScale(textScale)
            fg = textprops['fg']
            if fg:
                self.setFg(fg, textMaker)
            shadow = textprops['shadow']
            shadowOffset = textprops['shadowOffset']
            if shadow:
                self.setShadow(shadow, shadowOffset, textMaker)
            
            #prepared to add more props here
            
        else: textMaker = self.textMaker
        
        
        if newLine or not self.lines:
            self.startLine()

        if not speed:
            for word in text:
                self.appendWord(word, tm = textMaker, fadein = fadein, fadeinType = fadeinType)
        #TYPER EFFECT
        else:
            self.__TextLerpInit()
            self.__currentLerpInterval = LerpFunc(self._appendTextLerpFunc,extraArgs = [text,textMaker,fadein,fadeinType],
                                                  duration = len(text)/float(speed))
            self.__currentLerpInterval.start()
                
    def __TextLerpInit(self):
        if self.__currentLerpInterval:
            self.__currentLerpInterval.finish()
        self.__lerpLock.acquire()
        self.__lastTextLerpValue = 0
        self.__lerpLock.release()
                
    def _appendTextLerpFunc(self, lerp, text, tm, fadein, fadeinType):
        '''The function interval method for typer effect'''
        self.__lerpLock.acquire()
        tlen = len(text)
        start = int(math.floor(self.__lastTextLerpValue * tlen))
        end = int(math.floor(lerp * tlen))
        if end > start:
            appendingText = text[start:end]
            for word in appendingText:
                self.appendWord(word, tm, fadein = fadein, fadeinType = fadeinType)
        self.__lastTextLerpValue = lerp
        self.__lerpLock.release()
        
    def isWaiting(self):
        if self.__currentLerpInterval:
            return self.__currentLerpInterval.isPlaying()
        return False
    
    def quickFinish(self):
        if self.__currentLerpInterval:
            return self.__currentLerpInterval.finish()
        for l in self.lines:
            l.quickFinish()       

            
    def appendWord(self,word,tm = None, fadein = 0, fadeinType = 0):
        if word == '\n':
            self.startLine()
            return
        
        textMaker = tm or self.textMaker
        if not self.lines:
            self.startLine()
        
        active_line = self.lines[-1] 
        
        unicodeText = isinstance(word, types.UnicodeType)
        if unicodeText:
            textMaker.setWtext(word)
        else:
            textMaker.setText(word)
            
        width = textMaker.getWidth()
        height = textMaker.getHeight()
        node = textMaker.generate()
        textpath = NodePath('text_path')
        textpath.attachNewNode(node)
        if self.wordwrap:
            if active_line.getTotalWidth() + width > self.wordwrap:
                self.startLine()
                active_line = self.lines[-1]
        
        active_line.append(textpath, width, height,self.spacing, fadein = fadein, fadeinType = fadeinType)
        active_line.setPos(0,0,-(self.currentHeight + active_line.getLineHeight()) )
          
    def startLine(self):
        if self.lines:
            self.currentHeight += self.lines[-1].getLineHeight() + self.lineSpacing 
        line = TextLine(parent = self, height = self.minLineHeight)
        line.setPos(0,0,-self.currentHeight)
        self.lines.append(line)
    
    def removeNode(self, *args, **kwargs):
        return NodePath.removeNode(self, *args, **kwargs)
            
    def getCurrentText(self):
        return self.recordedText
    
    def getCopiedText(self):
        return copy.deepcopy(self.recordedText)
    
    def loadRecordedText(self,recorded):
        for section in recorded:
            self.appendStoredText(section)
        self.recordedText = copy.copy(recorded)
            
    def getNewText(self):
        if self.recordedText:
            return self.recordedText[0]['text']
        return ''
        
    def getEndPos(self):
        if self.lines:
            return (self.lines[-1].getEndPos()[0],0 , -(self.currentHeight + self.lines[-1].getLineHeight()))
        else: return (0,0,0)
        
    def hasContent(self):
        "get if this text label empty"
        return bool(self.lines)
コード例 #12
0
ファイル: sogal_text.py プロジェクト: PlumpMath/Sogal
class TextLine(NodePath):
    '''
    One line of SogalText, contains geom generated by TextNodes
    Text are unable to be changed
    '''
    def __init__(self,parent = None, height = 0):
        self.parent = parent or aspect2d
        
        NodePath.__init__(self,'line')
        self.reparentTo(self.parent)
        
        self.currentPtr = (0,0,0)
        self.lineHeight = height
        self.lineWidth = 0
        
        self.items = [] #each word/character is a NodePath
        self.__lerpIntervals = []
        self.__lock = Lock()
        
    def quickFinish(self):
        for l in self.__lerpIntervals:
            l.finish()
        
    def removeNode(self, *args, **kwargs):
        for l in self.__lerpIntervals:
            l.pause()
        del self.items
        return NodePath.removeNode(self, *args, **kwargs)
        
    def append(self,text,width,height,spacing = 0, fadein = 0, fadeinType = 0):
        '''Add a NodePath that contains a generated text geom
        This method is called by SogalText
        '''
        self.__lock.acquire()
        
        self.items.append(text)
        text.reparentTo(self)
        textPos = self.currentPtr
        text.setPos(textPos)
        if fadein:
            interval = Parallel()
            if fadeinType == 0 or fadeinType == 'normal':
                interval.append(LerpFunc(_modifyAlphaScale,fadein,0,1,blendType = 'easeOut',extraArgs = [text]))
            if fadeinType == 1 or fadeinType == 'flyin':
                interval.append(LerpFunc(_modifyAlphaScale,fadein,0,1,blendType = 'easeOut',extraArgs = [text]))
                interval.append(LerpPosInterval(text,fadein,
                                                self.currentPtr,
                                                (self.currentPtr[0],self.currentPtr[1],self.currentPtr[2] -0.3),
                                                blendType = 'easeOut'))            
            interval.start()
            self.__lerpIntervals.append(interval)
            
        self.lineWidth = self.currentPtr[0] + width
        self.lineHeight = max(height, self.lineHeight)
        self.currentPtr = (self.lineWidth + spacing, 0, 0)
        
        self.__lock.release()
        
    def getLineWidth(self):
        return self.lineWidth
    
    def getLineHeight(self):
        return self.lineHeight
    
    def getTotalWidth(self):
        return self.currentPtr[0]
    
    def getEndPos(self):
        return self.currentPtr