"""
This test gives an example of how some computation results from an
auxiliary thread could be 'published' via pubsub in a thread-safe
manner, in a 'gui'-like application, ie an application where the
main thread is in an infinite event loop and supports the callback
of user-defined functions when the gui is idle.

The worker thread 'work' is to increment a counter
as fast as interpreter can handle. Every so often (every resultStep counts),
the thread stores the count in a synchronized queue, for later retrieval
by the main thread. In parallel to this, the main thread loops forever (or
until user interrupts via keyboard), doing some hypothetical work
(represented by the sleep(1) call) and calling all registered 'idle'
callbacks. The transfer is done by extracting items from the queue and
publishing them via pubsub.

Oliver Schoenborn
May 2009

:copyright: Copyright 2008-2009 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE.txt for details.

"""


__author__="schoenb"
__date__ ="$31-May-2009 9:11:41 PM$"

from Queue import Queue
import time
import threading

from pubsub import pub
from pubsub.py2and3 import print_


resultStep = 1000000 # how many counts for thread "result" to be available


def threadObserver(transfers, threadObj, count):
    """Listener that listens for data from testTopic. This function
    doesn't know where the data comes from (or in what thread it was
    generated... but threadObj is the thread in which this
    threadObserver is called and should indicate Main thread)."""

    print_(transfers, threadObj, count / resultStep)

pub.subscribe(threadObserver, 'testTopic')


def onIdle():
    """This should be registered with 'gui' to be called when gui is idle
    so we get a chance to transfer data from aux thread without blocking
    the gui. Ie this function must spend as little time as possible so
    'gui' remains reponsive."""
    thread.transferData()


class ParaFunction(threading.Thread):
    """
    Represent a function running in a parallel thread. The thread
    just increments a counter and puts the counter value on a synchronized
    queue every resultStep counts. The content of the queue can be published by
    calling transferData().
    """

    def __init__(self):
        threading.Thread.__init__(self)
        self.running = False # set to True when thread should stop
        self.count = 0       # our workload: keep counting!
        self.queue = Queue() # to transfer data to main thread
        self.transfer = 0    # count how many transfers occurred

    def run(self):
        print_('aux thread started')
        self.running = True
        while self.running:
            self.count += 1
            if self.count % resultStep == 0:
                self.queue.put(self.count)

        print_('aux thread done')

    def stop(self):
        self.running = False

    def transferData(self):
        """Send data from aux thread to main thread. The data was put in
        self.queue by the aux thread, and this queue is a Queue.Queue which
        is a synchronized queue for inter-thread communication. 
        Note: This method must be called from main thread."""
        self.transfer += 1
        while not self.queue.empty():
            pub.sendMessage('testTopic',
                transfers = self.transfer,
                threadObj = threading.currentThread(),
                count     = self.queue.get())


thread = ParaFunction()


def main():
    idleFns = [] # list of functions to call when 'gui' idle
    idleFns.append( onIdle )

    try:
        thread.start()

        print_('starting event loop')
        eventLoop = True
        while eventLoop:
            time.sleep(1) # pretend that main thread does other stuff
            for idleFn in idleFns:
                idleFn()

    except KeyboardInterrupt:
        print_('Main interrupted, stopping aux thread')
        thread.stop()

    except Exception, exc:
        from pubsub import py2and3
        exc = py2and3.getexcobj()
        print_(exc)
        print_('Exception, stopping aux thread')
        thread.stop()


main()

