¿Podría volverme loco con los controladores de eventos? ¿Voy por el "camino equivocado" con mi diseño?

12

Creo que he decidido que realmente me gustan los controladores de eventos. Puede que esté sufriendo un poco de parálisis de análisis, pero me preocupa hacer que mi diseño sea difícil de manejar o tener alguna otra consecuencia imprevista de mis decisiones de diseño.

Mi motor de juego actualmente realiza renderizado básico basado en sprites con una cámara panorámica panorámica. Mi diseño se parece un poco a esto:

SceneHandler

Contiene una lista de clases que implementan la interfaz SceneListener (actualmente solo Sprites). Llama a render () una vez por tick y envía onCameraUpdate (); mensajes a SceneListeners.

InputHandler

Sondea la entrada una vez por marca y envía un mensaje simple "onKeyPressed" a InputListeners. Tengo un Camera InputListener que contiene una instancia de SceneHandler y desencadena updateCamera (); eventos basados ​​en lo que es la entrada.

AgentHandler

Invoca acciones predeterminadas en cualquier Agente (AI) una vez por tick, y verificará una pila de nuevos eventos que estén registrados, enviándolos a Agentes específicos según sea necesario.

Así que tengo objetos sprite básicos que pueden moverse por una escena y usar comportamientos de dirección rudimentarios para viajar. Llegué a la detección de colisión, y aquí es donde no estoy seguro de que la dirección de mi diseño sea buena. ¿Es una buena práctica tener muchos controladores de eventos pequeños? Me imagino que, tal como soy, tendría que implementar algún tipo de CollisionHandler.

¿Estaría mejor con un EntityHandler más consolidado que maneja AI, actualizaciones de colisión y otras interacciones de entidad en una clase? ¿O estaré bien simplemente implementando muchos subsistemas de manejo de eventos diferentes que se transmiten mensajes entre sí en función de qué tipo de evento es? ¿Debo escribir un EntityHandler que simplemente sea responsable de coordinar todos estos controladores de eventos secundarios?

Me doy cuenta de que en algunos casos, como mi InputHandler y SceneHandler, esos son tipos de eventos muy específicos. Una gran parte de mi código de juego no se preocupará por la entrada, y una gran parte no se preocupará por las actualizaciones que ocurren puramente en la representación de la escena. Por lo tanto, siento que mi aislamiento de esos sistemas está justificado. Sin embargo, estoy haciendo esta pregunta específicamente acerca de los eventos de tipo de lógica de juego.

sensae
fuente

Respuestas:

21

El diseño basado en eventos implica principalmente implementar el patrón de diseño del Reactor .

Antes de comenzar a diseñar componentes, debe echar un vistazo a las limitaciones de dicho patrón (puede encontrar mucha información sobre esto).

El primero y más importante de los problemas es que los controladores tienen que regresar rápidamente , ya que todos los que hicieron algún trabajo serio en marcos basados ​​en GUI y aplicaciones basadas en JavaScript lo saben bien.

Cuando desarrolle cada aplicación con una complejidad decente , tarde o temprano se enfrentará a un controlador que hace un trabajo complejo que requiere tiempo .

En estos casos, puede distinguir dos situaciones (no necesariamente mutuamente excluyentes):

Complejos relacionados con eventos responsabilidades y complejos por eventos no relacionados responsabilidades.

Responsabilidades complejas relacionadas con eventos:

En el primer caso, combinó demasiadas responsabilidades en un único controlador de componentes : se toman demasiadas acciones en respuesta a un evento o hay sincronizaciones en ejecución, por ejemplo.

En este caso, la solución es dividir el controlador en diferentes controladores (en diferentes objetos si es necesario) y dejar que un controlador active eventos en lugar de llamar directamente al código de manejo. Esto le permitirá permitir que se administre un evento de mayor prioridad antes ya que su controlador principal ha regresado después de agregar eventos a la cola de eventos.

Otra solución es permitir que una entidad externa active eventos y que los demás se suscriban si está interesado (el evento de tiempo es el ejemplo más trivial que puede generalizar).

Responsabilidades complejas no relacionadas con eventos:

El segundo caso ocurre cuando hay una complejidad intrínseca en la acción a tomar después de un evento: por ejemplo, debe calcular un número de Fibonacci después de un evento.

Aquí el patrón Reactor básicamente falla , vale la pena dividir el algoritmo de generación de Fibonacci en pequeños fragmentos que disparan eventos al finalizar para activar el siguiente paso.

Aquí la solución es mezclar un diseño roscado al reactor , la buena noticia es que si la complejidad es intrínseca (por lo que está leyendo la sección correcta) es muy probable que sea posible iniciar un hilo independiente que haga el trabajo y que necesita saber poco o nada sobre el resto de los componentes relacionados con el Reactor.

Para manejar este tipo de situaciones, me pareció útil adoptar una cola de trabajos sobre un grupo de subprocesos flexible .

Cuando un controlador necesita comenzar una acción larga, encapsula esa acción en una unidad de trabajo para colocarla en la cola de trabajos. Esta acción encapsulada debe tener un medio para desencadenar eventos en el hilo del reactor. El gestor de colas de trabajos puede ejecutarse en su propio subproceso o en el subproceso del reactor y reaccionar ante el evento "newJob".

El gestor de colas de trabajos hace lo siguiente:

  • asigna una unidad de trabajo a un subproceso desde el grupo de subprocesos flexibles utilizando su algoritmo shedule si el grupo puede proporcionar un subproceso (hay un subproceso libre o se permite una nueva creación de subprocesos)

  • escuche el grupo en sí para ver si una unidad de trabajo terminó para que se pueda recuperar un subproceso para ejecutar una unidad pendiente más, si corresponde.

Usando el mecanismo de disparo de eventos, una unidad de trabajo puede activar eventos para notificar la finalización, el estado de actualización, advertencias, errores o cuando sea necesario. El grupo de subprocesos flexibles garantiza un buen uso de los recursos y evita que los subprocesos muertos cuelguen todo el sistema; la cola de trabajos le permite elegir qué tipo de prioridad asignar a diferentes tipos de acciones, ya que sabe que requerirán una cantidad constante de recursos computacionales.

FxIII
fuente
3
+1 por ser muy minucioso. Algunos enlaces para el lector curioso serían increíbles.
sam hocevar
1

En la mayoría de las situaciones, el uso de eventos sobre otras opciones a menudo puede mejorar la velocidad y la descentralización. Creo que si puedes usar muchos eventos, deberías hacerlo.

anónimamente
fuente