changeset 422:52829ebe2561

Refactor window management in its own class.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Tue, 16 Jul 2013 21:07:15 +0200
parents b1248bab2d0f
children d8630c086926
files eosd pytouhou/lib/sdl.pxd pytouhou/lib/sdl.pyx pytouhou/ui/gamerenderer.py pytouhou/ui/gamerunner.py pytouhou/ui/window.py
diffstat 6 files changed, 194 insertions(+), 145 deletions(-) [+]
line wrap: on
line diff
--- a/eosd
+++ b/eosd
@@ -54,6 +54,7 @@ import logging
 import pyximport
 pyximport.install()
 
+from pytouhou.ui.window import Window
 from pytouhou.resource.loader import Loader
 from pytouhou.game.background import Background
 from pytouhou.ui.gamerunner import GameRunner
@@ -107,6 +108,8 @@ def main(path, data, stage_num, rank, ch
         sys.stderr.write('Some data files were not found, did you forget the -p option?\n')
         exit(1)
 
+    window = Window(double_buffer=(not single_buffer), fps_limit=fps_limit, fixed_pipeline=fixed_pipeline)
+
     if stage_num is None:
         story = True
         stage_num = 1
@@ -145,7 +148,7 @@ def main(path, data, stage_num, rank, ch
 
     game_class = EoSDGameBossRush if boss_rush else EoSDGame
 
-    runner = GameRunner(resource_loader, fps_limit=fps_limit, double_buffer=(not single_buffer), fixed_pipeline=fixed_pipeline, skip=skip_replay)
+    runner = GameRunner(window, resource_loader, skip=skip_replay)
     while True:
         if replay:
             level = replay.levels[stage_num - 1]
@@ -200,8 +203,9 @@ def main(path, data, stage_num, rank, ch
 
         # Main loop
         runner.load_game(game, background, stage.bgms, replay, save_keystates)
+        window.set_runner(runner)
         try:
-            runner.start()
+            window.run()
             break
         except NextStage:
             if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5):
--- a/pytouhou/lib/sdl.pxd
+++ b/pytouhou/lib/sdl.pxd
@@ -51,6 +51,8 @@ cdef extern from "SDL_video.h":
     void SDL_GL_DeleteContext(SDL_GLContext context)
     void SDL_DestroyWindow(SDL_Window *window)
 
+    void SDL_SetWindowSize(SDL_Window *window, int w, int h)
+
 
 cdef extern from "SDL_scancode.h":
     ctypedef enum SDL_Scancode:
--- a/pytouhou/lib/sdl.pyx
+++ b/pytouhou/lib/sdl.pyx
@@ -65,6 +65,9 @@ cdef class Window:
     def gl_delete_context(self):
         SDL_GL_DeleteContext(self.context)
 
+    def set_window_size(self, width, height):
+        SDL_SetWindowSize(self.window, width, height)
+
 
 cdef class Surface:
     cdef SDL_Surface *surface
--- a/pytouhou/ui/gamerenderer.py
+++ b/pytouhou/ui/gamerenderer.py
@@ -29,24 +29,8 @@ from .renderer import Renderer
 
 
 class GameRenderer(Renderer):
-    __slots__ = ('game', 'background')
-
-    def __init__(self, resource_loader, game=None, background=None):
+    def __init__(self, resource_loader):
         Renderer.__init__(self, resource_loader)
-        if game:
-            self.load_game(game, background)
-
-
-    def load_game(self, game=None, background=None):
-        self.game = game
-        self.background = background
-
-        if game:
-            # Preload textures
-            self.texture_manager.preload(game.resource_loader.instanced_anms.values())
-
-        if background:
-            self.prerender_background(background)
 
 
     def render(self):
--- a/pytouhou/ui/gamerunner.py
+++ b/pytouhou/ui/gamerunner.py
@@ -14,14 +14,9 @@
 
 from pytouhou.lib import sdl
 
-from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable, glDisable,
-                       glHint, glEnableClientState, glViewport, glScissor,
-                       glLoadMatrixf, glGenBuffers, glDeleteBuffers,
-                       GL_MODELVIEW, GL_PROJECTION,
-                       GL_TEXTURE_2D, GL_BLEND, GL_FOG,
-                       GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
-                       GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY,
-                       GL_SCISSOR_TEST)
+from pyglet.gl import (glMatrixMode, glEnable, glDisable, glViewport,
+                       glScissor, glLoadMatrixf, glGenBuffers, glDeleteBuffers,
+                       GL_MODELVIEW, GL_PROJECTION, GL_FOG, GL_SCISSOR_TEST)
 
 from pytouhou.utils.helpers import get_logger
 from pytouhou.utils.maths import perspective, setup_camera, ortho_2d
@@ -36,83 +31,19 @@ from ctypes import c_uint, byref
 logger = get_logger(__name__)
 
 
-class Clock(object):
-    def __init__(self, fps=None):
-        self._target_fps = 0
-        self._ref_tick = 0
-        self._ref_frame = 0
-        self._fps_tick = 0
-        self._fps_frame = 0
-        self._rate = 0
-        self.set_target_fps(fps)
-
-
-    def set_target_fps(self, fps):
-        self._target_fps = fps
-        self._ref_tick = 0
-        self._fps_tick = 0
-
-
-    def get_fps(self):
-        return self._rate
-
-
-    def tick(self):
-        current = sdl.get_ticks()
-
-        if not self._ref_tick:
-            self._ref_tick = current
-            self._ref_frame = 0
-
-        if self._fps_frame >= (self._target_fps or 60):
-            self._rate = self._fps_frame * 1000. / (current - self._fps_tick)
-            self._fps_tick = current
-            self._fps_frame = 0
-
-        self._ref_frame += 1
-        self._fps_frame += 1
+class GameRunner(GameRenderer):
+    def __init__(self, window, resource_loader, replay=None, skip=False):
+        GameRenderer.__init__(self, resource_loader)
 
-        target_tick = self._ref_tick
-        if self._target_fps:
-            target_tick += int(self._ref_frame * 1000 / self._target_fps)
-
-        if current <= target_tick:
-            sdl.delay(target_tick - current)
-        else:
-            self._ref_tick = current
-            self._ref_frame = 0
-
-
-class GameRunner(GameRenderer):
-    def __init__(self, resource_loader, game=None, background=None, replay=None, double_buffer=True, fps_limit=60, fixed_pipeline=False, skip=False):
-        GameRenderer.__init__(self, resource_loader, game, background)
-
-        sdl.init(sdl.INIT_VIDEO)
-        sdl.img_init(sdl.INIT_PNG)
-        sdl.mix_init(0)
-
-        sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2)
-        sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, 1)
-        sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, int(double_buffer))
-        sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24)
-
-        self.width, self.height = (game.interface.width, game.interface.height) if game else (640, 480)
-        self.win = sdl.Window('PyTouhou',
-                              sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
-                              self.width, self.height,
-                              sdl.WINDOW_OPENGL | sdl.WINDOW_SHOWN)
-        self.win.gl_create_context()
-
-        sdl.mix_open_audio(44100, sdl.DEFAULT_FORMAT, 2, 4096)
-        sdl.mix_allocate_channels(26) #TODO: make it dependent on the SFX number.
-
-        self.fps_limit = fps_limit
-        self.use_fixed_pipeline = fixed_pipeline
+        self.window = window
         self.replay_level = None
         self.skip = skip
-        self.has_exit = False
         self.keystate = 0
 
+        self.use_fixed_pipeline = window.use_fixed_pipeline #XXX
+        self.width = window.width #XXX
+        self.height = window.height #XXX
+
         if not self.use_fixed_pipeline:
             self.game_shader = GameShader()
             self.background_shader = BackgroundShader()
@@ -122,14 +53,16 @@ class GameRunner(GameRenderer):
             glGenBuffers(2, vbo_array)
             self.vbo, self.back_vbo = vbo_array
 
-        if game:
-            self.load_game(game, background, replay)
-
-        self.clock = Clock(self.fps_limit)
-
 
     def load_game(self, game=None, background=None, bgms=None, replay=None, save_keystates=None):
-        GameRenderer.load_game(self, game, background)
+        self.game = game
+        self.background = background
+
+        self.texture_manager.preload(game.resource_loader.instanced_anms.values())
+
+        if background:
+            self.prerender_background(background)
+
         self.set_input(replay)
         if replay and replay.levels[game.stage - 1]:
             game.players[0].state.lives = self.replay_level.lives
@@ -152,25 +85,11 @@ class GameRunner(GameRenderer):
             self.keys = self.replay_level.iter_keystates()
 
 
-    def set_size(self, width, height):
-        self.win.set_window_size(width, height)
-
-
-    def start(self, width=None, height=None):
-        width = width or (self.game.interface.width if self.game else 640)
-        height = height or (self.game.interface.height if self.game else 480)
+    def start(self):
+        width = self.game.interface.width if self.game else 640
+        height = self.game.interface.height if self.game else 480
         if (width, height) != (self.width, self.height):
-            self.set_size(width, height)
-
-        # Initialize OpenGL
-        glEnable(GL_BLEND)
-        if self.use_fixed_pipeline:
-            glEnable(GL_TEXTURE_2D)
-            glHint(GL_FOG_HINT, GL_NICEST)
-            glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
-            glEnableClientState(GL_COLOR_ARRAY)
-            glEnableClientState(GL_VERTEX_ARRAY)
-            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
+            self.window.set_size(width, height)
 
         self.proj = perspective(30, float(self.game.width) / float(self.game.height),
                                 101010101./2010101., 101010101./10101.)
@@ -178,26 +97,13 @@ class GameRunner(GameRenderer):
         self.game_mvp = game_view * self.proj
         self.interface_mvp = ortho_2d(0., float(self.width), float(self.height), 0.)
 
-        while not self.has_exit:
-            if not self.skip:
-                self.update()
-                self.render_game()
-                self.render_interface()
-                self.win.gl_swap_window()
-                self.clock.tick()
-            else:
-                self.update()
 
-        if not self.use_fixed_pipeline:
-            vbo_array = (c_uint * 2)(self.vbo, self.back_vbo)
-            glDeleteBuffers(2, vbo_array)
-
-        self.win.gl_delete_context()
-        self.win.destroy_window()
-        sdl.mix_close_audio()
-        sdl.mix_quit()
-        sdl.img_quit()
-        sdl.quit()
+    def finish(self):
+        #TODO: actually clean after buffers are not needed anymore.
+        #if not self.use_fixed_pipeline:
+        #    vbo_array = (c_uint * 2)(self.vbo, self.back_vbo)
+        #    glDeleteBuffers(2, vbo_array)
+        pass
 
 
     def update(self):
@@ -208,9 +114,9 @@ class GameRunner(GameRenderer):
             if type_ == sdl.KEYDOWN:
                 scancode = event[1]
                 if scancode == sdl.SCANCODE_ESCAPE:
-                    self.has_exit = True #TODO: implement the pause.
+                    return False #TODO: implement the pause.
             elif type_ == sdl.QUIT:
-                self.has_exit = True
+                return False
         if self.game:
             if not self.replay_level:
                 #TODO: allow user settings
@@ -246,6 +152,10 @@ class GameRunner(GameRenderer):
                 self.save_keystates.append(keystate)
 
             self.game.run_iter(keystate)
+        if not self.skip:
+            self.render_game()
+            self.render_interface()
+        return True
 
 
     def render_game(self):
@@ -263,7 +173,7 @@ class GameRunner(GameRenderer):
 
     def render_interface(self):
         interface = self.game.interface
-        interface.labels['framerate'].set_text('%.2ffps' % self.clock.get_fps())
+        interface.labels['framerate'].set_text('%.2ffps' % self.window.clock.get_fps())
 
         if self.use_fixed_pipeline:
             glMatrixMode(GL_MODELVIEW)
new file mode 100644
--- /dev/null
+++ b/pytouhou/ui/window.py
@@ -0,0 +1,146 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2013 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+
+from pytouhou.lib import sdl
+
+from pyglet.gl import (glEnable, glHint, glEnableClientState, GL_TEXTURE_2D,
+                       GL_BLEND, GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT,
+                       GL_NICEST, GL_COLOR_ARRAY, GL_VERTEX_ARRAY,
+                       GL_TEXTURE_COORD_ARRAY)
+
+
+class Clock(object):
+    def __init__(self, fps=None):
+        self._target_fps = 0
+        self._ref_tick = 0
+        self._ref_frame = 0
+        self._fps_tick = 0
+        self._fps_frame = 0
+        self._rate = 0
+        self.set_target_fps(fps)
+
+
+    def set_target_fps(self, fps):
+        self._target_fps = fps
+        self._ref_tick = 0
+        self._fps_tick = 0
+
+
+    def get_fps(self):
+        return self._rate
+
+
+    def tick(self):
+        current = sdl.get_ticks()
+
+        if not self._ref_tick:
+            self._ref_tick = current
+            self._ref_frame = 0
+
+        if self._fps_frame >= (self._target_fps or 60):
+            self._rate = self._fps_frame * 1000. / (current - self._fps_tick)
+            self._fps_tick = current
+            self._fps_frame = 0
+
+        self._ref_frame += 1
+        self._fps_frame += 1
+
+        target_tick = self._ref_tick
+        if self._target_fps:
+            target_tick += int(self._ref_frame * 1000 / self._target_fps)
+
+        if current <= target_tick:
+            sdl.delay(target_tick - current)
+        else:
+            self._ref_tick = current
+            self._ref_frame = 0
+
+
+
+class Window(object):
+    def __init__(self, size=None, double_buffer=True, fps_limit=60,
+                 fixed_pipeline=False, sound=True):
+        self.fps_limit = fps_limit
+        self.use_fixed_pipeline = fixed_pipeline
+        self.runner = None
+
+        sdl.init(sdl.INIT_VIDEO)
+        sdl.img_init(sdl.INIT_PNG)
+        if sound:
+            sdl.mix_init(0)
+
+        sdl.gl_set_attribute(sdl.GL_CONTEXT_MAJOR_VERSION, 2)
+        sdl.gl_set_attribute(sdl.GL_CONTEXT_MINOR_VERSION, 1)
+        sdl.gl_set_attribute(sdl.GL_DOUBLEBUFFER, int(double_buffer))
+        sdl.gl_set_attribute(sdl.GL_DEPTH_SIZE, 24)
+
+        self.width, self.height = size if size else (640, 480)
+
+        self.win = sdl.Window('PyTouhou',
+                              sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
+                              self.width, self.height,
+                              sdl.WINDOW_OPENGL | sdl.WINDOW_SHOWN)
+        self.win.gl_create_context()
+
+        # Initialize OpenGL
+        glEnable(GL_BLEND)
+        if self.use_fixed_pipeline:
+            glEnable(GL_TEXTURE_2D)
+            glHint(GL_FOG_HINT, GL_NICEST)
+            glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
+            glEnableClientState(GL_COLOR_ARRAY)
+            glEnableClientState(GL_VERTEX_ARRAY)
+            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
+
+        # Initialize sound
+        if sound:
+            sdl.mix_open_audio(44100, sdl.DEFAULT_FORMAT, 2, 4096)
+            sdl.mix_allocate_channels(26) #TODO: make it dependent on the SFX number.
+
+        self.clock = Clock(self.fps_limit)
+
+
+    def set_size(self, width, height):
+        self.win.set_window_size(width, height)
+
+
+    def set_runner(self, runner):
+        self.runner = runner
+        runner.start()
+
+
+    def run(self):
+        try:
+            while self.run_frame():
+                pass
+        finally:
+            self.runner.finish()
+
+
+    def run_frame(self):
+        if self.runner:
+            running = self.runner.update()
+        self.win.gl_swap_window()
+        self.clock.tick()
+        return running
+
+
+    def __dealloc__(self):
+        self.win.gl_delete_context()
+        self.win.destroy_window()
+        sdl.mix_close_audio()
+        sdl.mix_quit()
+        sdl.img_quit()
+        sdl.quit()