Durante uma das minhas palestras de hoje sobre o Unity, discutimos a atualização da posição do jogador, verificando cada quadro se o usuário tiver um botão pressionado. Alguém disse que isso era ineficiente e que deveríamos usar um ouvinte de evento.
Minha pergunta é: independentemente da linguagem de programação ou da situação em que é aplicada, como funciona um ouvinte de eventos?
Minha intuição presumiria que o ouvinte de eventos verifica constantemente se o evento foi disparado, o que significa que, no meu cenário, não seria diferente de verificar todos os quadros se o evento foi disparado.
Com base na discussão em sala de aula, parece que o ouvinte de eventos funciona de uma maneira diferente.
Como um ouvinte de eventos funciona?
event-programming
Gary Holiday
fonte
fonte
Respostas:
Diferente do exemplo de pesquisa que você forneceu (onde o botão é verificado em todos os quadros), um ouvinte de evento não verifica se o botão foi pressionado. Em vez disso, é chamado quando o botão é pressionado.
Talvez o termo "ouvinte de eventos" esteja jogando você. Esse termo sugere que o "ouvinte" está ativamente fazendo algo para ouvir, quando na verdade não está fazendo nada. O "ouvinte" é apenas uma função ou método que está inscrito no evento. Quando o evento é acionado, o método listener ("manipulador de eventos") é chamado.
O benefício do padrão de evento é que não há custo até que o botão seja realmente pressionado. O evento pode ser tratado dessa maneira sem ser monitorado porque se origina do que chamamos de "interrupção de hardware", que antecipa brevemente o código em execução para disparar o evento.
Algumas estruturas de interface do usuário e de jogos usam algo chamado "loop de mensagem", que enfileira eventos para execução em algum período posterior (geralmente curto), mas você ainda precisa de uma interrupção de hardware para inserir esse evento no loop de mensagens.
fonte
Um ouvinte de evento semelhante a uma assinatura de boletim informativo por email (você se registra para receber atualizações, cuja transmissão é iniciada posteriormente pelo remetente), em vez de atualizar continuamente uma página da Web (onde você está iniciando a transferência de informações).
Um sistema de eventos é implementado usando objetos de eventos, que gerenciam uma lista de assinantes. Objetos interessados (chamados assinantes , ouvintes , delegados etc.) podem se inscrever para serem informados de um evento chamando um método que se inscreve no evento, o que faz com que o evento os adicione à sua lista. Sempre que o evento é acionado (a terminologia também pode incluir: chamado , acionado , invocado , executado etc.), ele chama o método apropriado em cada um dos assinantes, para informá-los do evento, transmitindo as informações contextuais que eles precisam entender o que aconteceu.
fonte
A resposta curta e insatisfatória é que o aplicativo recebe um sinal (o evento) e que a rotina é chamada apenas nesse ponto.
A explicação mais longa é um pouco mais envolvida.
De onde vêm os eventos do cliente?
Cada aplicativo moderno † possui um "loop de eventos" interno, geralmente semi-oculto, que envia eventos para os componentes corretos que devem recebê-los. Por exemplo, um evento "clique" é enviado ao botão cuja superfície é visível nas coordenadas atuais do mouse. Isso está no nível mais simples. Na realidade, o sistema operacional faz muito esse envio, pois alguns eventos e alguns componentes receberão mensagens diretamente.
De onde vêm os eventos do aplicativo?
Os sistemas operacionais despacham eventos conforme eles acontecem. Eles fazem isso reativamente sendo notificados por seus próprios motoristas.
Como os drivers geram eventos?
Eu não sou especialista, mas com certeza alguns usam interrupções na CPU: o hardware que eles controlam gera um alfinete na CPU quando novos dados estão disponíveis; a CPU dispara o driver que lida com os dados recebidos, o que eventualmente gera uma (fila de) eventos a serem despachados e, em seguida, retorna o controle de volta ao sistema operacional.
Então, como você vê, seu aplicativo não está sendo executado o tempo todo. É um monte de procedimentos que são disparados pelo sistema operacional (sorta) à medida que os eventos acontecem, mas não fazem nada o resto do tempo.
† existem exceções notáveis, como jogos pela primeira vez, que podem fazer coisas de maneira diferente
fonte
Terminologia
evento : um tipo de coisa que pode acontecer.
disparo de evento : uma ocorrência específica de um evento; um evento acontecendo.
ouvinte de evento : algo que atenta para acionamentos de eventos.
manipulador de eventos : algo que ocorre quando um ouvinte de eventos detecta um disparo de evento.
assinante de evento : uma resposta que o manipulador de eventos deveria chamar.
Essas definições não dependem da implementação, portanto podem ser implementadas de maneiras diferentes.
Alguns desses termos geralmente são confundidos com sinônimos, pois muitas vezes não é necessário que os usuários façam distinção entre eles.
Cenários comuns
Eventos de lógica de programação.
O evento é quando algum método é chamado.
Um disparo de evento é uma chamada específica para esse método.
O ouvinte de evento é um gancho no método de evento chamado em cada disparo de evento que chama o manipulador de eventos.
O manipulador de eventos chama uma coleção de assinantes de eventos.
O (s) assinante (s) do evento executam todas as ações que o sistema pretende que aconteçam em resposta à ocorrência do evento.
Eventos externos.
O evento é um acontecimento externo que pode ser inferido a partir de observáveis.
Um disparo de evento é quando esse acontecimento externo pode ser reconhecido como tendo ocorrido.
O ouvinte de eventos de alguma forma detecta disparos de eventos, geralmente pesquisando os observáveis, e então chama o manipulador de eventos ao detectar um disparo de evento.
O manipulador de eventos chama uma coleção de assinantes de eventos.
O (s) assinante (s) do evento executam todas as ações que o sistema pretende que aconteçam em resposta à ocorrência do evento.
Pesquisa versus inserção de ganchos no mecanismo de disparo do evento
O argumento de outras pessoas é que as pesquisas geralmente não são necessárias. Isso ocorre porque os ouvintes de eventos podem ser implementados fazendo com que os acionamentos de eventos chamem automaticamente o manipulador de eventos, que geralmente é a maneira mais eficiente de implementar coisas quando os eventos são ocorrências no nível do sistema.
Por analogia, você não precisa verificar a caixa de correio todos os dias se o funcionário bater à sua porta e entregar a correspondência diretamente a você.
No entanto, os ouvintes de eventos também podem trabalhar pesquisando. A pesquisa não precisa necessariamente verificar um valor específico ou outro observável; pode ser mais complexo. Mas, em geral, o objetivo da pesquisa é inferir quando algum evento ocorreu, para que possa ser respondido.
Por analogia, você deve verificar sua caixa de correio todos os dias quando o funcionário dos correios simplesmente solta a correspondência nela. Você não precisaria fazer esse trabalho de votação se pudesse instruir o funcionário a bater à sua porta, mas isso geralmente não é uma possibilidade.
Encadeando a lógica do evento
Em muitas linguagens de programação, você pode escrever um evento chamado apenas quando uma tecla do teclado é pressionada ou em um determinado momento. Embora sejam eventos externos, você não precisa fazer uma pesquisa para eles. Por quê?
É porque o sistema operacional está pesquisando para você. Por exemplo, o Windows verifica itens como alterações de estado do teclado e, se detectar um, chama os assinantes do evento. Portanto, quando você se inscreve em um evento de pressionamento de teclado, na verdade está se inscrevendo em um evento que é ele próprio um assinante de um evento que é pesquisado.
Por analogia, diga que você está morando em um complexo de apartamentos e um funcionário dos correios envia as cartas para uma área de recebimento de correspondência. Em seguida, um trabalhador do tipo sistema operacional pode verificar se há correio para todos, entregando correio nos apartamentos daqueles que receberam algo. Isso poupa a todos os demais de ter que pesquisar a área de recebimento de correio.
Como você suspeitou, um evento pode funcionar através de pesquisas. E se um evento está de alguma forma relacionado a acontecimentos externos, por exemplo, uma tecla do teclado sendo pressionada, a pesquisa precisa acontecer em algum momento.
Também é verdade que os eventos não precisam necessariamente envolver pesquisas. Por exemplo, se o evento é quando um botão é pressionado, o ouvinte de evento desse botão é um método que a estrutura da GUI pode chamar quando determina que um clique do mouse bate no botão. Nesse caso, ainda era necessário realizar a pesquisa para que o clique do mouse fosse detectado, mas o ouvinte do mouse é um elemento mais passivo conectado ao mecanismo de pesquisa primitiva por meio do encadeamento de eventos.
Atualização: na pesquisa de hardware de baixo nível
Acontece que os dispositivos USB e outros protocolos de comunicação modernos têm um conjunto de protocolos bastante fascinante para interações, permitindo que dispositivos de E / S, incluindo teclados e mouses, se envolvam em topologias ad hoc .
Curiosamente, as " interrupções " são coisas bastante imperativas e síncronas, para que não lidem com topologias de rede ad hoc . Para corrigir isso, " interrupções " foram generalizadas em pacotes assíncronos de alta prioridade chamados " transações de interrupção " (no contexto do USB) ou " interrupções sinalizadas por mensagem " (no contexto do PCI). Este protocolo é descrito em uma especificação USB:
A essência parece ser que dispositivos de E / S e componentes de comunicação (como hubs USB) agem basicamente como dispositivos de rede. Então, eles enviam mensagens, o que requer a pesquisa de suas portas e tal. Isso alivia a necessidade de linhas de hardware dedicadas.
Sistemas operacionais como Windows parecem lidar com o próprio processo de votação, por exemplo, como descrito na documentação do MSDN para o
USB_ENDPOINT_DESCRIPTOR
's que descreve como controlar a frequência do Windows urnas um controlador de host USB para mensagens de interrupção / isócronos:Protocolos de conexão de monitor mais recentes, como o DisplayPort, parecem fazer o mesmo:
Essa abstração permite alguns recursos interessantes, como a execução de 3 monitores em uma conexão:
Conceitualmente, o ponto a se afastar disso é que os mecanismos de pesquisa permitem comunicações seriais mais generalizadas, o que é incrível quando você deseja uma funcionalidade mais geral. Portanto, o hardware e o SO fazem muitas pesquisas para o sistema lógico. Em seguida, os consumidores que se inscrevem em eventos podem aproveitar esses detalhes sendo tratados pelo sistema de nível inferior, sem precisar escrever seus próprios protocolos de pesquisa / transmissão de mensagens.
Por fim, eventos como pressionamentos de teclas parecem passar por uma série bastante interessante de eventos antes de chegar ao mecanismo imperativo de acionamento de eventos do nível de software.
fonte
Pull vs Push
Existem duas estratégias principais para verificar se um evento ocorreu ou se um estado específico é atingido. Por exemplo, imagine aguardar uma entrega importante:
A abordagem pull (também chamada de polling) é mais simples: você pode implementá-la sem nenhum recurso especial. Por outro lado, geralmente é menos eficiente, pois você corre o risco de fazer verificações extras sem nada para mostrar.
Por outro lado, a abordagem push é geralmente mais eficiente: seu código é executado apenas quando há algo a fazer. Por outro lado, requer que exista um mecanismo para você registrar um ouvinte / observador / retorno de chamada 1 .
1 Meu carteiro normalmente não possui esse mecanismo, infelizmente.
fonte
Sobre a unidade em específico - não há outra maneira de verificar a entrada do jogador além de pesquisar todos os quadros. Para criar um ouvinte de evento, você ainda precisaria de um objeto como "sistema de eventos" ou "gerenciador de eventos" para fazer a pesquisa, para que apenas levasse o problema a uma classe diferente.
É verdade que, depois de ter um gerente de eventos, você tem apenas uma classe pesquisando a entrada em cada quadro, mas isso não oferece vantagens óbvias de desempenho, já que agora essa classe precisa interagir com os ouvintes e chamá-los, que, dependendo do seu jogo design (por exemplo, quantos ouvintes existem e com que frequência o player utiliza entrada) pode ser realmente mais caro.
Além de tudo isso, lembre-se da regra de ouro - a otimização prematura é a raiz de todo mal , o que é especialmente verdadeiro nos videogames, onde geralmente o processo de renderização de cada quadro custa tanto, que pequenas otimizações de script como essa são completamente insignificantes
fonte
A menos que você tenha algum suporte no OS / Framework que lide com eventos como pressionar o botão ou estourar o timer ou a chegada de mensagens - você precisará implementar esse padrão do Event Listener usando a pesquisa de qualquer maneira (em algum lugar abaixo).
Mas não se afaste desse padrão de design apenas porque você não tem um benefício de desempenho imediatamente. Aqui estão as razões pelas quais você deve usá-lo, independentemente de ter suporte subjacente para manipulação de eventos ou não.
Conclusão - você teve a sorte de participar da discussão e aprendeu uma alternativa à votação. Procure uma oportunidade para aplicar esse conceito na prática e você apreciará o quão elegante o código pode ser.
fonte
A maioria dos loops de eventos é criada acima de uma primitiva de multiplexação de sondagem fornecida pelo sistema operacional. No Linux, essa primitiva geralmente é a chamada do sistema
poll
(2) (mas pode ser a antigaselect
). Nos aplicativos GUI, o servidor de exibição (por exemplo , Xorg ou Wayland ) está se comunicando (através de um soquete (7) ou tubo (7) ) com seu aplicativo. Leia também sobre os protocolos e a arquitetura do sistema X Window .Tais primitivas de votação são eficientes; na prática, o kernel ativaria seu processo quando alguma entrada fosse feita (e alguma interrupção fosse tratada).
Concretamente, a biblioteca do kit de ferramentas do widget se comunica com o servidor de exibição, aguardando mensagens e enviando essas mensagens para os widgets. Bibliotecas de kits de ferramentas como Qt ou GTK são bastante complexas (milhões de linhas de código fonte). Seu teclado e mouse são tratados apenas pelo processo do servidor de exibição (que converte essas entradas em mensagens de eventos enviadas para aplicativos clientes).
(Estou simplificando; de fato, as coisas são muito mais complexas)
fonte
Em um sistema puramente baseado em pesquisas, o subsistema que talvez queira saber quando alguma ação específica ocorre precisará executar algum código sempre que essa ação ocorrer. Se houver muitos subsistemas que precisariam reagir dentro de 10 ms após a ocorrência de algum evento não necessariamente único, todos precisariam verificar pelo menos 100 vezes / segundo se o evento havia ocorrido. Se esses subsistemas estiverem em processos diferentes de encadeamentos (ou pior, processos), isso exigiria a troca de cada encadeamento ou processo 100x / segundo.
Se muitas das coisas que os aplicativos observam são bastante semelhantes, pode ser mais eficiente ter um subsistema de monitoramento centralizado - talvez orientado por tabelas - que possa observar muitas coisas e observar se alguma delas foi alterada. Se houver 32 comutadores, por exemplo, uma plataforma poderá ter uma função para ler todos os 32 comutadores de uma só vez em uma palavra, possibilitando ao código do monitor verificar se algum comutador foi alterado entre pesquisas e - se não - não se preocupe com o código que pode estar interessado neles.
Se houver muitos subsistemas que desejam notificação quando algo mudar, ter um subsistema de monitoramento dedicado notificar outros subsistemas quando ocorrerem eventos nos quais eles estão interessados pode ser mais eficiente do que cada subsistema pesquisar seus próprios eventos. A configuração de um subsistema de monitoramento dedicado nos casos em que ninguém está interessado em nenhum evento, no entanto, representaria um puro desperdício de recursos. Se houver apenas alguns subsistemas interessados em eventos, o custo de vê-los assistir aos eventos nos quais eles estão interessados pode ser menor do que o custo de configurar um subsistema de monitoramento dedicado de uso geral, mas o ponto de equilíbrio O ponto varia significativamente entre as diferentes plataformas.
fonte
Um ouvinte de evento é como um ouvido aguardando uma mensagem. Quando o evento ocorre, a sub-rotina escolhida como ouvinte de eventos funciona usando os argumentos do evento.
Sempre existem dois dados importantes: o momento em que o evento acontece e o objeto em que esse evento ocorre. Outro argumento são mais dados sobre o que aconteceu.
O ouvinte de evento especifica a reação àquela que ocorre.
fonte
Um Ouvinte de Evento segue o Padrão de Publicação / Assinatura (como assinante)
Na sua forma mais simples, um objeto de publicação mantém uma lista de instruções dos assinantes a serem executadas quando algo precisar ser publicado.
Ele terá algum tipo de
subscribe(x)
método, em que x depende de como o manipulador de eventos foi projetado para lidar com o evento. Quando a inscrição (x) é chamada, x é adicionado à lista de editores das instruções / referências dos assinantes.O editor pode conter toda, parte ou nenhuma lógica para manipular o evento. Pode simplesmente exigir referências aos assinantes para notificá-los / transformá-los com sua lógica especificada quando o evento ocorrer. Pode não conter lógica e exigir objetos de assinante (métodos / ouvintes de eventos) que possam manipular o evento. É mais provável que contenha uma mistura de ambos.
Quando um evento ocorre, o editor itera e executa sua lógica para cada item da lista de instruções / referências dos assinantes.
Não importa a complexidade de um manipulador de eventos, ele segue esse padrão simples.
Exemplos
Para um exemplo de ouvinte de evento, você fornece um método / função / instrução / ouvinte de evento ao método subscribe () do manipulador de eventos. O manipulador de eventos adiciona o método à sua lista de retornos de chamada de assinante. Quando um evento ocorre, o manipulador de eventos itera sobre sua lista e executa cada retorno de chamada.
Para um exemplo do mundo real, quando você assina o boletim no Stack Exchange, uma referência ao seu perfil será adicionada a uma tabela de assinantes do banco de dados. Quando chegar a hora de publicar o boletim, a referência será usada para preencher um modelo do boletim e será enviada para seu email. Nesse caso, x é simplesmente uma referência a você, e o editor possui um conjunto de instruções internas usadas para todos os assinantes.
fonte