En mi juego, hay terrenos con edificios (casas, centros de recursos). Los edificios como las casas tienen inquilinos, habitaciones, complementos, etc., y hay varios valores que deben simularse en función de todas estas variables.
Ahora, me gustaría usar AndEngine para las cosas de front-end, y crear otro hilo para hacer los cálculos de simulación (quizás también más tarde incluya AI en este hilo). Esto es para que un hilo completo no haga todo el trabajo y cause problemas como el bloqueo. Esto introduce el problema de concurrencia y dependencia .
El problema de la moneda es mi hilo de interfaz de usuario principal y el hilo de cálculo necesitaría acceder a todos los objetos de simulación. Así que tengo que hacerlos seguros para subprocesos, pero no sé cómo almacenar y estructurar los objetos de simulación para permitir eso.
El problema de dependencia es que para calcular valores, mis cálculos dependen de los valores de otros objetos.
¿Cuál sería la mejor manera de vincular mi objeto de inquilino en el edificio con mis cálculos? ¿Codificarlo en la clase de inquilino? ¿Cuál es una buena manera de hacer algoritmos de "almacenamiento" para que se puedan modificar fácilmente?
Una manera simple y perezosa sería juntar todo en una clase que contenga todo el objeto, como parcelas de tierra (que a su vez sostienen los edificios, etc.). Esta clase también mantendría el estado del juego, como la tecnología disponible para el usuario, grupos de objetos para cosas como sprites. Pero esta es una manera perezosa y peligrosa, ¿correcto?
Editar: Estaba mirando la inyección de dependencia, pero ¿qué tan bien se las arregla como una clase que contiene otros objetos? es decir, mi parcela de tierra, con un edificio, que tiene un inquilino y una gran cantidad de otros valores. DI también parece un dolor en el trasero con AndEngine.
fuente
Respuestas:
Su problema es inherentemente serial: debe completar una actualización de la simulación antes de poder procesarla. Descargar la simulación a un subproceso diferente simplemente significa que el subproceso de la interfaz de usuario principal no hace nada mientras el subproceso de simulación funciona (lo que significa que está bloqueado).
La "práctica recomendada" más común para la concurrencia es no poner su representación en un hilo y su simulación en otro, como está proponiendo. Realmente recomiendo contra ese enfoque, de hecho. Las dos operaciones están naturalmente relacionadas en serie, y aunque pueden ser forzadas, no es óptimo y no escala .
Un mejor enfoque es hacer que partes de la actualización o representación sean simultáneas, pero dejar que la actualización y la representación sean siempre en serie. Entonces, por ejemplo, si tiene un límite natural en su simulación (por ejemplo, si las casas nunca se afectan entre sí en su simulación), puede empujar todas las casas en cubos de N casas, y girar un montón de hilos que cada uno procesa y deje que esos hilos se unan antes de que se complete el paso de actualización. Esto escala mucho mejor y se adapta mucho mejor al diseño concurrente.
Estás pensando demasiado el resto del problema:
La inyección de dependencia es una pista falsa aquí: toda inyección de dependencia realmente significa que usted pasa ("inyecta") las dependencias de una interfaz a instancias de esa interfaz, generalmente durante la construcción.
Eso significa que si tiene una clase que modela a
House
, que necesita saber cosas sobre el estado enCity
que se encuentra, entonces elHouse
constructor podría verse así:Nada especial.
El uso de un singleton es innecesario (a menudo lo ves hecho en algunos de los "marcos DI" increíblemente complejos y sobredimensionados como Caliburn que están diseñados para aplicaciones GUI "empresariales"; esto no lo convierte en una buena solución). De hecho, la introducción de singletons es a menudo la antítesis de una buena gestión de dependencias. También pueden causar serios problemas con el código multiproceso porque generalmente no se pueden hacer seguros para subprocesos sin bloqueos: cuantos más bloqueos deba adquirir, peor será su problema para manejarlo de forma paralela.
fuente
La solución habitual para problemas de concurrencia es el aislamiento de datos .
El aislamiento significa que cada subproceso tiene sus propios datos y no toca los datos de otros subprocesos. De esta manera no hay problemas con la concurrencia ... pero luego tenemos problemas de comunicación. ¿Cómo pueden estos hilos trabajar juntos si no comparten ningún dato?
Hay dos enfoques aquí.
El primero es la inmutabilidad . Las estructuras / variables inmutables son las que nunca cambian su estado. Al principio, esto puede sonar inútil: ¿cómo se puede usar una "variable" que nunca cambia? Sin embargo, ¡podemos intercambiar estas variables! Considere este ejemplo: suponga que tiene una
Tenant
clase con un montón de campos, que debe estar en algún estado consistente. Si cambia unTenant
objeto en el hilo A, y al mismo tiempo lo observa desde el hilo B, el hilo B puede ver el objeto en estado inconsistente. Sin embargo, siTenant
es inmutable, el hilo A no puede cambiarlo. En cambio, crea nuevosTenant
objeto con campos configurados según sea necesario, y lo intercambia con el anterior. El intercambio es solo un cambio a una referencia, que probablemente sea atómica, y por lo tanto no hay forma de observar el objeto en estado inconsistente.El segundo enfoque es la mensajería . La idea detrás de esto es que cuando todos los datos son "propiedad" de algún hilo, podemos decirle a este hilo qué hacer con los datos. Cada subproceso en esta arquitectura tiene una cola de mensajes (una lista de
Message
objetos y una bomba de mensajería) que ejecuta constantemente un método que elimina un mensaje de la cola, lo interpreta y llama a algún método de controlador. Por ejemplo, suponga que hizo tapping en una parcela de tierra, lo que indica que debe comprarse. El subproceso de la interfaz de usuario no puede cambiar elPlot
objeto directamente, porque pertenece al subproceso lógico (y probablemente sea inmutable). Por lo tanto, el subproceso de interfaz de usuario construye unBuyMessage
objeto y lo agrega a la cola del subproceso lógico. El hilo lógico, cuando se ejecuta, toma el mensaje de la cola y llamaBuyPlot()
, extrayendo los parámetros del objeto de mensaje. Es posible que envíe un mensaje de vuelta, por ejemploBuySuccessfulMessage
, indicando al subproceso de la interfaz de usuario que coloque un mensaje "¡Ahora tienes más tierra!" ventana en pantalla. Por supuesto, el acceso a la cola de mensajes debe estar sincronizado con el bloqueo, la sección crítica o como se llame en AndEngine. Pero este es un punto único de sincronización entre subprocesos, y los subprocesos se suspenden por un tiempo muy corto, por lo que no es un problema.Estos dos enfoques se utilizan mejor en combinación. Sus hilos deben comunicarse con mensajes y tener algunos datos inmutables "abiertos" para otros hilos, por ejemplo, una lista inmutable de diagramas para que la interfaz de usuario los dibuje.
¡Tenga en cuenta también que "solo lectura" no significa necesariamente inmutable ! Cualquier estructura de datos compleja como una tabla hash puede cambiar su estado interno en los accesos de lectura, por lo tanto, consulte primero la documentación.
fuente
Probablemente el 99% de los programas de computadora escritos en la historia usaron solo 1 hilo y funcionaron bien. No tengo ninguna experiencia con AndEngine, pero es muy raro encontrar sistemas que requieran subprocesos, solo algunos que podrían haberse beneficiado con el hardware adecuado.
Tradicionalmente, para hacer simulación y GUI / renderizado en un hilo, simplemente hace un poco de la simulación, luego renderiza y repite, generalmente muchas veces por segundo.
Cuando alguien tiene poca experiencia en el uso de múltiples procesos, o no aprecia completamente lo que significa 'seguridad' de hilo (que es un término vago que puede significar muchas cosas diferentes), es demasiado fácil introducir muchos errores en un sistema. Por lo tanto, personalmente recomendaría adoptar el enfoque de un solo subproceso, intercalar la simulación y el renderizado, y guardar cualquier subproceso para operaciones que sabe con certeza que llevarán mucho tiempo y requerirán subprocesos y no un modelo basado en eventos.
fuente