Eu tenho um código que quero executar apenas uma vez, mesmo que as circunstâncias que acionam esse código possam ocorrer várias vezes.
Por exemplo, quando o usuário clica no mouse, desejo clicar na coisa:
void Update() {
if(mousebuttonpressed) {
ClickTheThing(); // I only want this to happen on the first click,
// then never again.
}
}
No entanto, com esse código, toda vez que clico no mouse, a coisa é clicada. Como posso fazer isso acontecer apenas uma vez?
architecture
events
MichaelHouse
fonte
fonte
Respostas:
Use uma bandeira booleana.
No exemplo mostrado, você modificaria o código para algo como o seguinte:
Além disso, se você quiser repetir a ação, mas limite a frequência da ação (ou seja, o tempo mínimo entre cada ação). Você usaria uma abordagem semelhante, mas redefinir a bandeira após um certo período de tempo. Veja minha resposta aqui para mais idéias sobre isso.
fonte
onStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }
evoid doSomething() { doStuff(); delegate = funcDoNothing; }
no final, todas as opções acabam tendo uma bandeira de algum tipo que você define / desativa ... e delegar nesse caso não é mais nada (exceto se você tiver mais de duas opções, o que fazer, talvez?).Se o sinalizador bool não for suficiente ou você quiser melhorar a legibilidade * do código no
void Update()
método, considere o uso de delegados (ponteiros de função):Para simples "executar uma vez" delegados são um exagero, então eu sugiro usar o sinalizador bool.
No entanto, se você precisar de funcionalidades mais complicadas, os delegados provavelmente são a melhor opção. Por exemplo, se você deseja executar em cadeia mais ações diferentes: uma no primeiro clique, outra no segundo e mais uma no terceiro, você pode simplesmente fazer:
em vez de atormentar seu código com dezenas de sinalizadores diferentes.
* ao custo de menor manutenção do restante do código
Fiz alguns perfis com os resultados exatamente como eu esperava:
O primeiro teste foi executado no Unity 5.1.2, medido com
System.Diagnostics.Stopwatch
um projeto construído de 32 bits (não no designer!). O outro no Visual Studio 2015 (v140) compilado no modo de liberação de 32 bits com o sinalizador / Ox. Ambos os testes foram executados na CPU Intel i5-4670K a 3.4GHz, com 10.000.000 de iterações para cada implementação. código:Conclusão: Enquanto o compilador Unity faz um bom trabalho ao otimizar chamadas de função, fornecendo aproximadamente o mesmo resultado para sinalizadores positivos e delegados (21 e 25 ms respectivamente), a previsão incorreta do ramo ou a chamada de função ainda é bastante cara (nota: delegado deve ser considerado cache neste teste).
Curiosamente, o compilador Unity não é inteligente o suficiente para otimizar a ramificação quando houver 99 milhões de previsões erradas consecutivas; portanto, a negação manual do teste gera algum aumento de desempenho, oferecendo o melhor resultado de 5 ms. A versão C ++ não mostra nenhum aumento de desempenho para negar a condição, no entanto, a sobrecarga geral da chamada de função é significativamente menor.
mais importante: a diferença é praticamente irrelevante para qualquer cenário do mundo real
fonte
Para completar
(Na verdade, não recomendo que você faça isso, pois é bastante simples escrever algo como
if(!already_called)
, mas seria "correto" fazê-lo.)Sem surpresa, o padrão C ++ 11 identificou o problema bastante trivial de chamar uma função uma vez e a tornou super explícita:
}
É certo que a solução padrão é um pouco superior à trivial na presença de threads, pois ainda garante que sempre exatamente uma chamada acontece, nunca algo diferente.
No entanto, você normalmente não está executando um loop de eventos multithread e pressionando os botões de vários mouse ao mesmo tempo, para que a segurança do thread seja um pouco supérflua para o seu caso.
Em termos práticos, isso significa que, além da inicialização segura por thread de um booleano local (que o C ++ 11 garante a segurança de thread de qualquer maneira), a versão padrão também deve executar uma operação atômica test_set antes de chamar ou não chamar a função.
Ainda assim, se você gosta de ser explícito, existe a solução.
EDIT:
Huh. Agora que estou sentado aqui, encarando o código por alguns minutos, estou quase inclinado a recomendá-lo.
Na verdade, não é tão tolo quanto pode parecer à primeira vista; na verdade, comunica muito clara e inequivocamente sua intenção ... indiscutivelmente melhor estruturada e mais legível do que qualquer outra solução que envolva uma
if
ou outra .fonte
Algumas sugestões variam dependendo da arquitetura.
Crie clickThisThing () como um ponteiro de função / variante / DLL / so / etc ... e verifique se ele é inicializado com a função necessária quando o objeto / componente / módulo / etc ... é instanciado.
No manipulador, sempre que o botão do mouse é pressionado, chame o ponteiro da função clickThisThing () e substitua imediatamente o ponteiro por outro ponteiro da função NOP (sem operação).
Não há necessidade de sinalizadores e lógica extra para alguém estragar mais tarde, basta chamá-lo e substituí-lo todas as vezes e, como é um NOP, o NOP não faz nada e é substituído por um NOP.
Ou você pode usar o sinalizador e a lógica para pular a chamada.
Ou você pode desconectar a função Update () após a chamada para que o mundo exterior a esqueça e nunca mais a chame.
Ou você tem o próprio clickThisThing () usando um desses conceitos para responder apenas com trabalho útil uma vez. Dessa forma, você pode usar um objeto / componente / módulo clickThisThingOnce () da linha de base e instancia-lo em qualquer lugar que precise desse comportamento e nenhum dos seus atualizadores precisa de lógica especial em todo o lugar.
fonte
Use ponteiros de função ou delegados.
A variável que contém o ponteiro da função não precisa ser explicitamente examinada até que uma alteração precise ser feita. E quando você não precisar mais da lógica para executar, substitua por ref para uma função vazia / sem operação. Compare with doing condicionals verifica todos os quadros durante toda a vida útil do seu programa - mesmo se esse sinalizador fosse necessário apenas nos primeiros quadros após a inicialização! - Imundo e ineficiente. (O ponto de vista de Boreal sobre previsão é, no entanto, reconhecido a esse respeito.)
As condicionais aninhadas, que muitas vezes constituem, são ainda mais caras do que ter que verificar uma única condicional a cada quadro, pois a previsão de ramificação não pode mais fazer sua mágica nesse caso. Isso significa atrasos regulares da ordem de 10s de ciclos - barragens de oleodutos por excelência . O aninhamento pode ocorrer acima ou abaixo desse sinalizador booleano. Eu quase não preciso lembrar aos leitores como os loops de jogos complexos podem se tornar rapidamente.
Existem indicadores de função por esse motivo - use-os!
fonte
Em algumas linguagens de script, como javascript ou lua, pode ser feito facilmente testando a referência de função. Em Lua ( love2d ):
fonte
Em um computador com arquitetura Von Neumann Architecture , a memória é onde as instruções e os dados do seu programa são armazenados. Se você deseja que o código seja executado apenas uma vez, é possível que o código substitua o método pelos NOPs depois que o método for concluído. Dessa forma, se o método fosse executado novamente, nada aconteceria.
fonte
Em python, se você planeja ser um idiota para outras pessoas no projeto:
este script demonstra o código:
fonte
Aqui está outra contribuição: é um pouco mais geral / reutilizável, um pouco mais legível e sustentável, mas provavelmente menos eficiente do que outras soluções.
Vamos encapsular nossa lógica de execução única em uma classe, Once:
Usá-lo como o exemplo fornecido é o seguinte:
fonte
Você pode considerar o uso de uma variável estática.
Você pode fazer isso na
ClickTheThing()
própria função ou criar outra função e chamarClickTheThing()
o corpo if.É semelhante à idéia de usar um sinalizador, como sugerido em outras soluções, mas o sinalizador está protegido contra outro código e não pode ser alterado em nenhum outro lugar devido ao local da função.
fonte
Na verdade, me deparei com esse dilema com a entrada do Xbox Controller. Embora não seja exatamente o mesmo, é bem parecido. Você pode alterar o código no meu exemplo para atender às suas necessidades.
Edit: Sua situação usaria isso ->
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse
E você pode aprender como criar uma classe de entrada bruta via ->
https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input
Mas .. agora no algoritmo super impressionante ... não realmente, mas ei .. é muito legal :)
* Então ... podemos armazenar os estados de cada botão e quais são Pressionados, Liberados e Retidos !!! Também podemos verificar o tempo de espera, mas isso requer uma única declaração if e pode verificar qualquer número de botões, mas existem algumas regras, veja abaixo para obter essas informações.
Obviamente, se quisermos verificar se alguma coisa foi pressionada, liberada, etc. você faria "If (This) {}", mas isso está mostrando como podemos obter o estado da impressora e depois desligá-lo no próximo quadro para que você " ismousepressed "será realmente falso na próxima vez que você verificar.
Código completo aqui: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp
Como funciona..
Portanto, não tenho certeza dos valores que você recebe quando mostra se um botão é pressionado ou não, mas basicamente quando carrego no XInput, recebo um valor de 16 bits entre 0 e 65535, que possui estados possíveis de 15 bits para "Pressed".
O problema era que toda vez que eu checava isso, simplesmente me informava o estado atual das informações. Eu precisava de uma maneira de converter o estado atual em valores pressionados, liberados e retidos.
Então, o que eu fiz é o seguinte.
Primeiro, criamos uma variável "CURRENT". Toda vez que verificamos esses dados, configuramos o "CURRENT" como uma variável "ANTERIOR" e, em seguida, armazenamos os novos dados em "Atual", como visto aqui ->
Com essas informações, é aqui que fica emocionante !!
Agora podemos descobrir se um botão está sendo pressionado!
O que isso faz é basicamente comparar os dois valores e qualquer pressionamento de botão mostrado em ambos permanecerá 1 e todo o resto definido como 0.
Ou seja, (1 | 2 | 4) e (2 | 4 | 8) produzirão (2 | 4).
Agora que temos quais botões estão "pressionados". Nós podemos descansar.
Pressionado é simples. Pegamos nosso estado "ATUAL" e removemos todos os botões pressionados.
Liberado é o mesmo, apenas o comparamos ao nosso último estado.
Então, olhando para a situação Pressed. Se vamos dizer que atualmente tivemos 2 | 4 8 pressionado. Descobrimos que 2 | 4 onde realizada. Quando removemos os Bits Retidos, ficamos com apenas 8. Este é o bit recém-pressionado para este ciclo.
O mesmo pode ser aplicado para Liberado. Nesse cenário "ÚLTIMO" foi definido como 1 | 2 4. Então, quando removemos o 2 | 4 bits. Ficamos com 1. Portanto, o botão 1 foi liberado desde o último quadro.
Esse cenário acima é provavelmente a situação mais ideal que você pode oferecer para comparação de bits e fornece 3 níveis de dados sem instruções if ou para loops apenas 3 cálculos rápidos de bits.
Eu também queria documentar dados de retenção, por isso, embora minha situação não seja perfeita ... o que faz é basicamente definir os valores de retenção que queremos verificar.
Portanto, toda vez que configuramos nossos dados de Imprensa / Liberação / Espera, verificamos se os dados de retenção ainda são iguais à atual verificação de bits de retenção. Caso contrário, redefinimos a hora para a hora atual. No meu caso, estou configurando-o para índices de quadros, para que eu saiba quantos quadros foram retidos.
A desvantagem dessa abordagem é que não posso obter tempos de espera individuais, mas você pode verificar vários bits ao mesmo tempo. Ou seja, se eu definir o bit de espera como 1 | 16 se 1 ou 16 não forem mantidos, isso falhará. Portanto, é necessário que TODOS esses botões sejam pressionados para continuar marcando.
Então, se você olhar no código, verá todas as chamadas de função.
Portanto, seu exemplo seria reduzido a simplesmente verificar se um pressionamento de botão ocorreu e um pressionamento de botão pode ocorrer apenas uma vez com esse algoritmo. Na próxima verificação para pressionar, ele não existirá, pois você não pode pressionar mais do que uma vez, pois seria necessário soltar antes de pressionar novamente.
fonte
Saída barata estúpida, mas você pode usar um loop for
fonte