为什么 pyglet.app.run() 重复调度 on_resize() 会导致最大递归深度错误?

Why is pyglet.app.run() dispatching on_resize() repeatedly leading to max recursion depth error?

我正在尝试制作一个简单的 pyglet 应用程序(使用 Python 3.7),我可以在其中绘制网格、一些瓷砖、为它们着色等。同时还能够平移、缩放,所以我跟着这个问题:

How to pan and zoom properly in 2D?

但是 on_resize() 子类的 on_resize() 事件 似乎在 run() 被调用后被重复调用,即使我没有触及 window,并且脚本以 最大递归深度 崩溃,除非我注释掉了它的 self.width = widthself.height = height 行。 但是然后调整 window 的大小会挤压我正在绘制的对象...

我想调整大小以显示更多地图,同时保持绘制对象的纵横比和大小。

我添加了一些 print() 调用来检查发生了什么,但我真的不知道这里发生了什么。

import pyglet
from pyglet.gl import *

from algorithm.priorityqueue import PriorityQueue
from algorithm.squaregrid import heuristic, SquareGrid

ZOOM_IN_FACTOR = 1.2
ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR

class Triangle:
    def __init__(self, color):
        self.color = color
        self.vertices = pyglet.graphics.vertex_list(3, ('v3f', [0,0,0, 1000,0,0, 1000,1000,0]),
                                                       ('c4B', self.color*3))

    def draw(self, x, y, z):
        glTranslatef(x, y, z)
        self.vertices.draw(GL_TRIANGLES)
        glTranslatef(-x, -y, -z)

class Tile:

    def __init__(self, size, color):
        self.size = size
        self.color = color
        self.vertices = pyglet.graphics.vertex_list(
                                        4, ('v3f', [0,0,0, 0+self.size,0,0, 
                                        0+self.size,0+self.size,0, 0,0+self.size,0]),
                                        ('c4B', self.color*4))

    def draw(self, x, y, z):
        glTranslatef(x, y, z)
        self.vertices.draw(GL_QUADS)
        glTranslatef(-x, -y, -z)

class Line:

    def __init__(self, start, end, color, stroke):
        self.color = color
        self.stroke = stroke
        self.sx, self.sy = start
        self.ex, self.ey = end

        self.vertices = pyglet.graphics.vertex_list(
                                        2, ('v3f', [self.sx,self.sy,0, self.ex,self.ey,0]),
                                        ('c4B', self.color*2))

    def draw(self):
        glLineWidth(self.stroke)
        self.vertices.draw(GL_LINES)


class MyWindow(pyglet.window.Window):
    def __init__(self, width, height, *args, **kwargs):
        conf = Config(sample_buffers=1,
                      samples=4,
                      depth_size=16,
                      double_buffer=True)
        super().__init__(width, height, config=conf, *args, **kwargs)

        #self.set_minimum_size(960, 540)
        #glClearColor(0.8, 0.8, 0.8, 1.0)

        #glOrtho(0, self.width, 0, self.height, -10, 10) # setup orthogonal projection

        self.left = 0
        self.right = width
        self.bottom = 0
        self.top = height
        self.zoom_level = 1
        self.zoomed_width = width
        self.zoomed_height = height
        print("init finished")

    def init_gl(self, width, height):
        print("init_gl started")
        # Clear color
        glClearColor(255/255, 255/255, 255/255, 255/255)

        # Antialiasing
        glEnable(GL_LINE_SMOOTH)
        glEnable(GL_POLYGON_SMOOTH)
        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)

        # Alpha Blending
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        # Viewport
        glViewport(0, 0, width, height)


    def drawstuff(self):
        print("drawstuff called")
        self.triangle = Triangle((255, 0, 0, 255))
        self.square = Tile(10, (255,0,255,255))

        self.gridlines = []
        for j in range(0,25):
            self.gridlines.append(Line((0, 40*j),(1000, 40*j),(50,50,50,255), 2))
        for i in range(0,25):
            self.gridlines.append(Line((40*i, 0),(40*i, 1000),(50,50,50,255), 2))       

    def on_resize(self, width, height):

        print("on_resize called")

        # It crashes here!
        self.width = width
        print(width)
        self.height = height
        print(height)

        self.init_gl(width, height)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        # Move camera
        self.left -= dx*self.zoom_level
        self.right -= dx*self.zoom_level
        self.bottom -= dy*self.zoom_level
        self.top -= dy*self.zoom_level       

    def on_mouse_scroll(self, x, y, dx, dy):
        # Scale factor
        f = ZOOM_IN_FACTOR if dy<0 else ZOOM_OUT_FACTOR if dy>0 else 1
        # If in proper range
        if .2 < self.zoom_level*f < 5:

            self.zoom_level *= f

            print(self.width, self.height)

            mouse_x = x/self.width
            mouse_y = y/self.height

            mouse_x_in_world = self.left + mouse_x * self.zoomed_width
            mouse_y_in_world = self.bottom + mouse_y * self.zoomed_height

            self.zoomed_width *= f
            self.zoomed_height *= f

            self.left = mouse_x_in_world - mouse_x * self.zoomed_width
            self.right = mouse_x_in_world + (1 - mouse_x) * self.zoomed_width
            self.bottom = mouse_y_in_world - mouse_y * self.zoomed_height
            self.top = mouse_y_in_world + (1 - mouse_y) * self.zoomed_height

    def on_draw(self):
        print("draw called")
        # Init projection matrix
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        # Init Modelview matrix
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        # Save default modelview matrix
        glPushMatrix()

        # Clear window with ClearColor
        glClear(GL_COLOR_BUFFER_BIT)

        # Set Orthographic projection matrix
        glOrtho(self.left, self.right, self.bottom, self.top, 1, -1)

        #self.draw_background()
        self.triangle.draw(0, 0, 0)
        self.square.draw(-100, -100, 0)
        for line in self.gridlines:
            line.draw()

        # Remove default modelview matrix
        glPopMatrix()

    def run(self):
        print("run called")
        pyglet.app.run()
        print("run finished")



if __name__ == "__main__":
    App = MyWindow(800, 500, resizable=True)
    App.drawstuff()
    App.run()

并且控制台显示:

$ python3 environments/testapp.py
init finished
drawstuff called
run called
on_resize called
on_resize called
on_resize called
on_resize called
on_resize called

等在某些时候 :

on_resize called
on_resize called
Traceback (most recent call last):
  File "environments/testapp.py", line 185, in <module>
    App.run()
  File "environments/testapp.py", line 177, in run
    pyglet.app.run()
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/app/__init__.py", line 107, in run
    event_loop.run()
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/app/base.py", line 159, in run
    self._legacy_setup()
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/app/base.py", line 182, in _legacy_setup
    window.dispatch_pending_events()
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/xlib/__init__.py", line 914, in dispatch_pending_events
    EventDispatcher.dispatch_event(self, *self._event_queue.pop(0))
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/event.py", line 415, in dispatch_event
    if getattr(self, event_type)(*args):
  File "environments/testapp.py", line 110, in on_resize
    self.width = width
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/__init__.py", line 964, in width
    self.set_size(new_width, self.height)
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/xlib/__init__.py", line 574, in set_size
    self.dispatch_event('on_resize', width, height)
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/__init__.py", line 1323, in dispatch_event
    if EventDispatcher.dispatch_event(self, *args) != False:
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/event.py", line 415, in dispatch_event
    if getattr(self, event_type)(*args):
  File "environments/testapp.py", line 110, in on_resize
    self.width = width
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/__init__.py", line 964, in width
    self.set_size(new_width, self.height)
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/xlib/__init__.py", line 574, in set_size
    self.dispatch_event('on_resize', width, height)
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/window/__init__.py", line 1323, in dispatch_event
    if EventDispatcher.dispatch_event(self, *args) != False:

最后 10 行重复了一会儿,最后:

if EventDispatcher.dispatch_event(self, *args) != False:
  File "/home/admin/.local/lib/python3.7/site-packages/pyglet/event.py", line 415, in dispatch_event
    if getattr(self, event_type)(*args):
  File "environments/testapp.py", line 108, in on_resize
    print("on_resize called")
RecursionError: maximum recursion depth exceeded while calling a Python object

我觉得 run() 自己调用 on_resize() 很奇怪,我真的不知道为什么要重新分配属性 self.width 和 self.height进入递归。

当分配的值不是宽度或高度,而是任何常量,以及 Window 实例是否作为参数传递 resizable = True= False 时,它也会崩溃

run不调用on_resize(),但是运行执行事件循环,初始化window后resize事件发生一次。
pyglet.window提供了属性.width.height,因此不需要分别用self.width = width设置self.height = height。给 self.widthself.height 赋值可能会触发 on_resize 事件。如果您在 on_resize 中执行此操作,则会导致无休止的递归。

self.widthself.height 包含 window 的当前大小,可以通过打印 on_resize 中的值轻松验证:

class MyWindow(pyglet.window.Window):
    # [...]

    def on_resize(self, width, height):
        print("on_resize called")
        print(self.width, width)
        print(self.height, height)

不要给self.widthself.height赋值,如果新值等于当前值:

class MyWindow(pyglet.window.Window):
    # [...]

    def on_resize(self, width, height):

        new_width = ...
        new_height = ...

        if new_width != width:
            self.width = new_width
        if new_height != height:
            self.height = new_height