realitix/vulk

View on GitHub
vulk/baseapp.py

Summary

Maintainability
A
1 hr
Test Coverage
from abc import ABC, abstractmethod
from collections import namedtuple
import logging

from vulk.audio import VulkAudio
from vulk.context import VulkWindow, VulkContext
from vulk.event import CallbackEventListener
from vulk.util import millis, time_since_millis

__all__ = ['BaseApp']

logger = logging.getLogger()


class BaseApp(ABC):
    '''App must inherit this class

    The App is responsible of creating the window and manage the life
    cycle of the program.
    '''

    def __init__(self, name='Vulk', x=-1, y=-1, width=640, height=480,
                 fullscreen=False, resizable=True, decorated=True,
                 highdpi=False, debug=False, extra_vulkan_layers=None,
                 audio_channel=8):
        # pylint: disable=W0612,W0613
        '''Set initial configuration

        *Parameters:*

        - `name`: Name of the application
        - `x`: X position of the window
        - `y`: Y position of the window
        - `width`: Width of the window
        - `height`: Height of the window
        - `fullscreen`: Should the window be in fullscreen mode
        - `resizable`: Should the window be resizable
        - `decorated`: Should the window be decorated (button close)
        - `highdpi`: Enable highdpi mode if supported
        - `debug`: Enable debug mode (for development only)
        - `extra_vulkan_layers`: `list` of custom vulkan layers

        **Note: When full screen mode is enabled, you can set width and
                height to 0 to use the native resolution, otherwise the
                fullscreen resolution will be set to width/height.**
        '''
        c = {k: v for k, v in locals().items() if k != 'self'}
        self.configuration = namedtuple('AppConfiguration', c.keys())(**c)

        self.last_time = millis()
        self._init_logger()
        self.context = None
        self.window = None
        self.audio = None
        self.event_listeners = []
        self.request_quit = False

    def _init_logger(self):
        if self.configuration.debug:
            logger.setLevel(logging.DEBUG)
        else:
            logger.setLevel(logging.WARNING)

        formatter = logging.Formatter('%(asctime)s :: %(levelname)s '
                                      ':: %(message)s')
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        stream_handler.setLevel(logging.DEBUG)
        logger.addHandler(stream_handler)

    def __enter__(self):
        '''Create window, Vulkan context, audio'''
        window = VulkWindow()
        window.open(self.configuration)
        self.context = VulkContext(window, self.configuration.debug,
                                   self.configuration.extra_vulkan_layers)
        self.context.create()
        self.audio = VulkAudio()
        self.audio.open(self.configuration)
        self.start()
        self.resize()
        return self

    def __exit__(self, *args):
        '''Clean Vulkan resource'''
        self.end()
        self.audio.close()
        self.context.window.close()

    def quit(self):
        '''Quit the App
        This function must be called to quit App
        '''
        self.request_quit = True

    def run(self):
        '''Start the game loop'''
        self.last_time = millis()

        while not self.request_quit:
            events = self.context.get_events()
            for event in events:
                for listener in self.event_listeners:
                    listener.handle(event)

            delta = time_since_millis(self.last_time)
            self.last_time = millis()
            self.render(delta)

    @abstractmethod
    def render(self, delta):
        '''Here your game loop.

        In this function, all your game is playing.

        *Parameters:*

        - delta: The delta time since the last frame in milliseconds
        '''
        return

    @abstractmethod
    def start(self):
        '''This function is called when your App starts'''
        listener = CallbackEventListener(
            quit=self.quit,
            window_resized=lambda width, height: self.resize()
        )
        self.event_listeners.append(listener)

    @abstractmethod
    def end(self):
        '''This function is called when your App ends'''
        return

    @abstractmethod
    def resize(self):
        width = self.context.width
        height = self.context.height
        logger.debug(f"Window resized to {width}x{height}")
        self.context.resize()


class Screen(ABC):
    def __init__(self):
        self.context = None

    @abstractmethod
    def render(self, delta):
        return

    @abstractmethod
    def show(self):
        return

    @abstractmethod
    def hide(self):
        self.context = None

    @abstractmethod
    def resize(self):
        return


class MultiScreenApp(BaseApp):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._screen = None

    @property
    def screen(self):
        return self._screen

    @screen.setter
    def screen(self, screen):
        if self._screen:
            self._screen.hide()
            self._screen.context = None
        if screen:
            self._screen = screen
            screen.context = self.context
            screen.show()
            screen.resize()

    def render(self, delta):
        super().render(delta)
        if self._screen:
            self._screen.render(delta)

    def resize(self):
        super().resize()
        if self._screen:
            self._screen.resize()

    def end(self):
        self.screen = None