# $Id: TwistedRenderer.py,v 1.7.2.1 2006/08/16 09:30:32 marcusva Exp $
#
# Copyright (c) 2005, Benjamin Olsen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""A Renderer that plays nicely with a threadedselectreactor from
Twisted.

Many thanks to Bob Ippolito for his work on threadedselectreactor and
the pygamedemo.py he created.

Written by Ben Olsen <ben@rickstranger.com>
"""

from pygame import QUIT, event
from pygame import error as PygameError
from pygame import time as PygameTime
from ocempgui.widgets import Renderer
from ocempgui.widgets.Constants import *

class TwistedRenderer (Renderer):
    """TwistedRenderer () -> TwistedRenderer

    A Renderer that allows the easy integration with Twisted.
    
    Because Twisted's threadedselectreactor *must* be shut down before
    the main loop shuts down, this Renderer will keep running until
    explicitly told to stop.

    Before starting the main loop, this Renderer will check to see if it
    has a Twisted reactor attached to it. This is an attribute set like
    any of the normal Renderer attributes:

    self.reactor = reactor

    If self.reactor is None (default), this will behave like a normal
    Renderer. If self.reactor has been set, the QUIT signal will call
    reactor.stop(), and then wait for reactor.addSystemEventTrigger to
    call self.stop(). This function will then stop the main loop.

    Usage
    -----
    Install the threadedselectreactor instead of the default reactor:

    from twisted.internet.threadedselectreactor import install
    install()
    from twisted.internet import reactor

    In the main section of your program, where you create the Renderer,
    just set TwistedRenderer's reactor:

    re = TwistedRenderer()
    re.reactor = reactor

    Everything else is handled internally by TwistedRenderer.

    Attributes:
    reactor - The twisted reactor attached to the TwistedRenderer.
    """
    def __init__ (self):
        Renderer.__init__ (self)
        self._reactor = None
        self._running = False
        
    def start (self):
        """T.start () -> None
        
        Overrides default start() to use self._running. If a reactor is
        attached, interleave self.waker
        """
        self._running = 1
        if self._reactor != None:
            self._reactor.interleave (self.waker)
        self._loop()
        
    def stop (self):
        """T.stop () -> None

        Tells the internal loop to stop running.
        """
        self._running = False
        
    def set_reactor (self, reactor):
        """T.set_reactor (...) -> None

        Sets the internal reactor.
        """
        if not hasattr (reactor, 'interleave'):
            raise AttributeError ("interleave() method not found in %s" %
                                  reactor)
        self._reactor = reactor
        self._reactor.addSystemEventTrigger ('after', 'shutdown', self.stop)

    def waker (self, func):
        """T.waker (...) -> None

        Used in threadedselectreactor.interleave.
        """
        event.post (event.Event (SIG_TWISTED, data=func))

    def distribute_events (self, *events):
        """T.distribute_events (...) -> None

        Overrides default distribute_events() to check for a reactor. If
        a reactor is found, the QUIT signal will call reactor.stop(). If
        there's no reactor attached, a QUIT signal will simply set
        self._running to False.
        """
        for event in events:
            if event.type == QUIT:
                if self._reactor != None:
                    self._reactor.stop ()
                else:
                    self._running = False

            elif event.type == SIG_TWISTED:
                event.data ()
            else:
                Renderer.distribute_events (self, (event))
        return True

    def _loop (self):
        """T._loop () -> None

        Overrides default _loop() so that it will not stop until
        self._running is false.
        """
        PygameTime.set_timer (SIG_TICK, 10)
        _post = event.post
        update = event.Event (SIG_UPDATED, data=self)
        
        while self._running:
            if self._postupdate:
                try:
                    _post (update)
                except PygameError: # Avoid "queue full" exceptions.
                    pass 
                self._postupdate = False
            
            # Get events and distribute them.
            events = event.get ()

            if not self.distribute_events (*events):
                return  # QUIT event
            if self.timer > 0:
                PygameTime.delay (1000 / self.timer)
            
    reactor = property (lambda self: self._reactor,
                        lambda self, var: self.set_reactor (var),
                        doc = "The twisted reactor attached to the Renderer.")
