Tengo un código que solo quiero ejecutar una vez, aunque las circunstancias que desencadenan ese código podrían ocurrir varias veces.
Por ejemplo, cuando el usuario hace clic con el mouse, quiero hacer clic en la cosa:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
Sin embargo, con este código, cada vez que hago clic con el mouse, se hace clic en la cosa. ¿Cómo puedo hacer que suceda solo una vez?
architecture
events
MichaelHouse
fuente
fuente
Respuestas:
Usa una bandera booleana.
En el ejemplo que se muestra, modificaría el código para que se parezca a lo siguiente:
Además, si desea poder repetir la acción, pero limite la frecuencia de la acción (es decir, el tiempo mínimo entre cada acción). Usaría un enfoque similar, pero restablecería la bandera después de un cierto período de tiempo. Vea mi respuesta aquí para obtener más ideas al respecto.
fuente
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
yvoid doSomething() { doStuff(); delegate = funcDoNothing; }
al final, todas las opciones terminan teniendo una bandera de algún tipo que establezca / desarme ... y delegar en este caso no es otra cosa (excepto si tiene más de dos opciones, ¿qué hacer quizás?).Si el indicador bool no es suficiente o si desea mejorar la legibilidad * del código en el
void Update()
método, podría considerar el uso de delegados (punteros de función):Para simples "ejecutar una vez" los delegados son excesivos, por lo que sugeriría usar la bandera bool en su lugar.
Sin embargo, si necesita una funcionalidad más complicada, los delegados son probablemente la mejor opción. Por ejemplo, si desea encadenar, ejecute más acciones diferentes: una en el primer clic, otra en el segundo y una más en la tercera, simplemente puede hacer:
en lugar de plagar tu código con decenas de banderas diferentes.
* a costa de una menor mantenibilidad del resto del código
Hice algunos perfiles con resultados más o menos como esperaba:
La primera prueba se ejecutó en Unity 5.1.2, medido con
System.Diagnostics.Stopwatch
un proyecto integrado de 32 bits (¡no en el diseñador!). El otro en Visual Studio 2015 (v140) compilado en modo de lanzamiento de 32 bits con el indicador / Ox. Ambas pruebas se ejecutaron en la CPU Intel i5-4670K a 3.4GHz, con 10,000,000 de iteraciones para cada implementación. código:conclusión: Si bien el compilador de Unity hace un buen trabajo al optimizar las llamadas a funciones, dando aproximadamente el mismo resultado tanto para el indicador positivo como para los delegados (21 y 25 ms respectivamente), la predicción errónea de la rama o la llamada a la función sigue siendo bastante costosa (nota: se debe asumir que el delegado debe tener caché en esta prueba).
Curiosamente, el compilador Unity no es lo suficientemente inteligente como para optimizar la rama cuando hay 99 millones de predicciones erróneas consecutivas, por lo que la negación manual de la prueba produce un cierto aumento del rendimiento que da el mejor resultado de 5 ms. La versión C ++ no muestra ningún aumento de rendimiento para negar la condición, sin embargo, la sobrecarga general de la llamada de función es significativamente menor.
Lo más importante: la diferencia es prácticamente irrelevante para cualquier escenario del mundo real
fuente
Por completitud
(En realidad, no recomiendo que hagas esto, ya que en realidad es bastante sencillo escribir algo así
if(!already_called)
, pero sería "correcto" hacerlo).Como era de esperar, el estándar C ++ 11 ha idiomizado el problema bastante trivial de llamar a una función una vez, y lo ha hecho súper explícito:
}
Es cierto que la solución estándar es algo superior a la trivial en presencia de hilos, ya que aún garantiza que siempre ocurra exactamente una llamada, nunca algo diferente.
Sin embargo, normalmente no está ejecutando un bucle de eventos multiproceso y presionando los botones de varios ratones al mismo tiempo, por lo que la seguridad del subproceso es un poco superflua para su caso.
En términos prácticos, significa que además de la inicialización segura para subprocesos de un booleano local (que C ++ 11 garantiza que es seguro para subprocesos de todos modos), la versión estándar también debe realizar una operación atómica test_set antes de llamar o no llamar la función.
Aún así, si te gusta ser explícito, ahí está la solución.
EDITAR:
Eh. Ahora que he estado sentado aquí, mirando ese código durante algunos minutos, estoy casi inclinado a recomendarlo.
En realidad, no es tan tonto como puede parecer al principio, de hecho, comunica de manera muy clara e inequívoca su intención ... posiblemente mejor estructurada y más legible que cualquier otra solución que implique uno
if
o tal.fuente
Algunas sugerencias variarán según la arquitectura.
Cree clickThisThing () como un puntero / variante de función / DLL / so / etc ... y asegúrese de que se inicialice a su función requerida cuando se instancia el objeto / componente / módulo / etc ...
En el controlador cada vez que se presiona el botón del mouse, llame al puntero de función clickThisThing () y luego reemplace inmediatamente el puntero con otro puntero de función NOP (sin operación).
No hay necesidad de banderas y lógica adicional para que alguien se equivoque más tarde, solo llámelo y reemplácelo cada vez y dado que es un NOP, el NOP no hace nada y se reemplaza con un NOP.
O puede usar la bandera y la lógica para omitir la llamada.
O puede desconectar la función Update () después de la llamada para que el mundo exterior se olvide de ella y nunca la vuelva a llamar.
O puede hacer que el clickThisThing () use uno de estos conceptos para responder solo con trabajo útil una vez. De esta manera, puede usar un objeto / componente / módulo clickThisThingOnce () de línea de base e instanciarlo en cualquier lugar donde necesite este comportamiento y ninguno de sus actualizadores necesita una lógica especial en todo el lugar.
fuente
Utilice punteros de función o delegados.
La variable que contiene el puntero de función no necesita ser examinada explícitamente hasta que se necesite hacer un cambio. Y cuando ya no necesite dicha lógica para ejecutar, reemplace con una referencia a una función vacía / no operativa. Compare con hacer verificaciones condicionales en cada cuadro durante toda la vida de su programa, ¡incluso si ese indicador solo fuera necesario en los primeros cuadros después del inicio! - Impuro e ineficiente. (Sin embargo, el punto de Boreal sobre la predicción se reconoce a este respecto).
Los condicionales anidados, que a menudo constituyen, son aún más costosos que tener que verificar un solo condicional en cada cuadro, ya que la predicción de rama ya no puede hacer su magia en ese caso. Eso significa retrasos regulares en el orden de 10s de ciclos: puestos de ductos por excelencia . El anidamiento puede ocurrir arriba o debajo de esta bandera booleana. No necesito recordarle a los lectores cuán complejos pueden ser los bucles de juego rápidamente.
Los punteros de función existen por este motivo. ¡Úselos!
fuente
En algunos lenguajes de script como javascript o lua se puede hacer fácilmente probando la referencia de función. En Lua ( love2d ):
fuente
En una computadora Von Neumann Architecture , la memoria es donde se almacenan las instrucciones y los datos de su programa. Si desea que el código se ejecute solo una vez, puede hacer que el código del método sobrescriba el método con NOP una vez que el método esté completo. De esta manera, si el método se volviera a ejecutar, no pasaría nada.
fuente
En python si planeas ser un imbécil con otras personas en el proyecto:
Este script demonestra el código:
fuente
Aquí hay otra contribución, es un poco más general / reutilizable, un poco más legible y mantenible, pero probablemente menos eficiente que otras soluciones.
Encapsulemos nuestra lógica de ejecución única en una clase, Una vez:
Usarlo como el ejemplo proporcionado se ve de la siguiente manera:
fuente
Puede considerar usar static una variable.
Puede hacer esto en la
ClickTheThing()
función misma, o crear otra función y llamarClickTheThing()
al cuerpo if.Es similar a la idea de usar un indicador como se sugiere en otras soluciones, pero el indicador está protegido de otro código y no puede modificarse en ningún otro lugar debido a que es local para la función.
fuente
De hecho, me encontré con este dilema con la entrada del controlador Xbox. Aunque no es exactamente lo mismo, es muy similar. Puede cambiar el código en mi ejemplo para satisfacer sus necesidades.
Editar: su situación usaría esto ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
Y puede aprender cómo crear una clase de entrada sin formato a través de ->
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Pero ... ahora en el algoritmo súper impresionante ... no realmente, pero bueno ... es bastante genial :)
* Entonces ... ¡podemos almacenar los estados de cada botón y cuáles son presionados, liberados y retenidos! También podemos verificar el Tiempo de retención, pero eso requiere una sola declaración if y puede verificar cualquier número de botones, pero hay algunas reglas que se encuentran a continuación para obtener esta información.
Obviamente, si queremos comprobar si se presiona, suelta, etc., debe hacer "If (This) {}", pero esto muestra cómo podemos obtener el estado de la prensa y luego desactivarlo en el siguiente cuadro para que su " ismousepressed "en realidad será falso la próxima vez que verifique.
Código completo aquí: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Cómo funciona..
Por lo tanto, no estoy seguro de los valores que recibe cuando describe si se presiona un botón o no, pero básicamente cuando cargo en XInput obtengo un valor de 16 bits entre 0 y 65535, esto tiene 15 bits de estados posibles para "Presionado".
El problema era que cada vez que revisaba esto simplemente me daría el estado actual de la información. Necesitaba una forma de convertir el estado actual en valores presionados, liberados y retenidos.
Entonces, lo que hice es lo siguiente.
Primero creamos una variable "ACTUAL". Cada vez que verificamos estos datos, establecemos "ACTUAL" en una variable "ANTERIOR" y luego almacenamos los nuevos datos en "Actual" como se ve aquí ->
¡Con esta información, aquí es donde se pone emocionante!
¡Ahora podemos averiguar si un botón se está DETENIENDO!
Lo que esto hace es básicamente que compara los dos valores y cualquier pulsación de botón que se muestra en ambos permanecerá 1 y todo lo demás se establecerá en 0.
Es decir (1 | 2 | 4) y (2 | 4 | 8) rendirán (2 | 4).
Ahora que tenemos qué botones están "HELD" hacia abajo. Podemos obtener el resto.
Presionado es simple ... tomamos nuestro estado "ACTUAL" y eliminamos cualquier botón presionado.
Lanzado es el mismo, solo que lo comparamos con nuestro ÚLTIMO estado.
Entonces mirando la situación presionada. Si digamos que actualmente teníamos 2 | 4 | 8 presionado. Encontramos que 2 | 4 donde se celebró. Cuando eliminamos los bits retenidos, solo nos queda 8. Este es el bit recién presionado para este ciclo.
Lo mismo se puede aplicar para Liberado. En este escenario, "ÚLTIMO" se estableció en 1 | 2 | 4. Entonces, cuando quitamos el 2 | 4 bits Nos quedamos con 1. Entonces, el botón 1 se soltó desde el último fotograma.
Este escenario anterior es probablemente la situación más ideal que puede ofrecer para la comparación de bits y proporciona 3 niveles de datos sin sentencias if o para bucles, solo 3 cálculos rápidos de bits.
También quería documentar los datos retenidos, así que aunque mi situación no es perfecta ... lo que hace es básicamente establecer los holdbits que queremos verificar.
Por lo tanto, cada vez que configuramos nuestros datos de Prensa / Liberación / Retención, verificamos si los datos de retención aún son iguales a la verificación de bit de retención actual. Si no es así, restablecemos la hora actual. En mi caso, lo configuro en índices de cuadros para saber cuántos cuadros se han mantenido presionados.
La desventaja de este enfoque es que no puedo obtener tiempos de espera individuales, pero puede verificar varios bits a la vez. Es decir, si establezco el bit de retención en 1 | 16 si no se mantienen 1 o 16, esto fallará. Por lo tanto, se requiere que TODOS esos botones se mantengan presionados para continuar marcando.
Luego, si observa el código, verá todas las llamadas a funciones ordenadas.
Por lo tanto, su ejemplo se reduciría a simplemente verificar si se presionó un botón y solo se puede presionar un botón con este algoritmo. En la próxima comprobación para presionar, no existirá, ya que no puede presionar más de una vez que tendría que soltar antes de poder presionar nuevamente.
fuente
Estúpida salida barata pero podrías usar un bucle for
fuente