Instalação sob demanda para jogos
Este artigo técnico aborda duas técnicas, instalação sob demanda e instalação em segundo plano, usando o Windows Installer. Os jogos podem utilizar essas técnicas de instalação para proporcionar uma experiência de jogo melhor e mais agradável para os jogadores, reduzindo o tempo de instalação.
- Visão geral
- Suporte à aplicação de patch
- O exemplo do SDK InstallOnDemand
- Os recursos e componentes do exemplo
- Instalação
- Limitação
Visão geral
A instalação tem sido um elemento de aplicativos baseados em computador há muito tempo. A maioria dos aplicativos atualmente exige que eles sejam instalados no disco rígido local do usuário primeiro antes que possam ser usados. Jogos de computador não são exceção; quando um consumidor compra um jogo do Microsoft Windows e tenta executá-lo, ele deve primeiro passar por um processo de instalação que copia os arquivos necessários do disco do jogo para o disco rígido. Esse processo de instalação geralmente é longo e pode levar até uma hora para ser concluído. O tempo de instalação é um fator que torna os jogos de console mais desejáveis do que os jogos baseados em computador para alguns jogadores, pois eles são capazes de jogar imediatamente um jogo de console depois de inserir o disco do jogo. A tecnologia discutida neste artigo tentará remediar isso reduzindo drasticamente o tempo de instalação.
Tradicionalmente, os jogos exigem que todos ou a maioria dos arquivos sejam instalados antes da inicialização. Para obter a instalação sob demanda, os recursos do jogo precisam ser modulares; ou seja, o desenvolvedor deve dividir os recursos do aplicativo (gráficos, áudio e assim por diante) em componentes. Cada componente é um conjunto de recursos que podem ser instalados ou removidos como uma unidade. Depois que isso é feito, o desenvolvedor de jogos define um ou mais recursos, normalmente um ou mais por nível ou zona. Cada recurso de um aplicativo especifica um conjunto de componentes necessários para executar esse recurso específico. Quando um aplicativo é instalado, seus recursos podem ser marcados como "instalação" (componentes copiados para o disco rígido local no momento da instalação) ou "anunciados" (componentes copiados para o disco rígido local após a instalação inicial quando o aplicativo usa esse recurso). Um desenvolvedor de jogos pode reduzir o tempo de instalação projetando o jogo para iniciar e executar com um conjunto mínimo de recursos instalados. O restante de seus recursos pode ser marcado como anunciado e instalado sob demanda quando o aplicativo realmente precisa usar as funcionalidades que esses recursos fornecem.
Os jogos podem chamar o Windows Installer para instalar um recurso específico que pode não ter sido instalado. Para fazer a instalação aparecer em segundo plano, um thread de trabalho pode ser usado para fazer as chamadas para o instalador enquanto o thread primário continua a manipular a lógica do jogo e renderizar a tela. Isso minimiza a interrupção da jogabilidade causada pela instalação. O jogo pode iniciar a instalação a qualquer momento. No entanto, como a instalação consome ciclos de processador, geralmente não é uma boa ideia executar a instalação quando o thread primário precisa de energia de processamento, como quando o usuário está no meio da ação. Por exemplo, um bom momento para executar a instalação pode ser durante o tempo em que o usuário está no menu do jogo, quando o jogo é pausado ou minimizado, ou quando o usuário está assistindo ao filme de introdução ou cutscenes.
Suporte à aplicação de patch
A maioria dos jogos de hoje precisa ser atualizada mesmo depois que eles são enviados à medida que os bugs são corrigidos e novos recursos são adicionados. A atualização geralmente requer aplicação de patch, que tradicionalmente é um procedimento simples para jogos. Como todos os arquivos necessários são instalados no disco rígido do usuário, a aplicação de patch de um jogo envolve a cópia de arquivos revisados no disco rígido, substituindo arquivos existentes. Quando a instalação sob demanda é empregada, nem todos os arquivos são instalados e copiados no momento da aplicação de patch. Portanto, o patcher não pode simplesmente gravar arquivos atualizados na pasta do jogo.
O Windows Installer tem recursos para aplicação de patch que usam instalação sob demanda. Ao aplicar um patch, o instalador armazena o patch em cache no sistema. Esse recurso funciona bem para patches com deltas pequenos. Os arquivos liberados originalmente não precisam mais estar no disco no momento da aplicação de patch, para que esses arquivos possam ser anunciados. Posteriormente, quando o aplicativo é executado e precisa acessar os arquivos, o instalador instala a versão mais recente desses arquivos copiando a versão lançada originalmente da mídia (por exemplo, CDs) e aplicando o patch depois de ler os dados salvos do patch.
O exemplo do SDK InstallOnDemand
O exemplo Instalar sob Demanda para Jogos demonstra as técnicas de instalação sob demanda discutidas neste artigo. Ao contrário de outros exemplos, a Instalação sob Demanda para Jogos não pode ser executada diretamente do navegador de exemplo. Como o exemplo usa o Windows Installer para gerenciar sua instalação, ele precisa ser incluído no banco de dados do instalador de aplicativos instalados.
Para iniciar o exemplo
- Use o link Instalar Projeto no navegador de exemplo para copiar os arquivos do exemplo para uma pasta.
- Clique duas vezes em InstallOnDemand.msi para instalar o exemplo.
- Selecione Instalação Típica.
- Inicie o exemplo iniciando o InstallOnDemand.exe na pasta instalada (normalmente Arquivos de Programas\InstallOnDemand) ou iniciando do Menu Iniciar\Programas.
InstallOnDemand.msi é um banco de dados reconhecido pelo instalador. Ele define todo o processo de instalação: a estrutura do diretório, o que será e não será copiado, quais recursos serão copiados juntos, quais valores do Registro gravar, quais atalhos criar e assim por diante.
Quando iniciado, o exemplo reproduz uma sequência de introdução. O player pode encerrá-lo e inserir o menu principal pressionando a tecla ESC. Após a introdução, o jogador pode iniciar um novo jogo inserindo um nome de caractere e rolando pelas estatísticas. Antes que o exemplo comece a reproduzir a sequência de introdução, o exemplo chama uma função de instalador para verificar se o recurso de nível 1 está instalado. Se o recurso de nível 1 não tiver sido instalado, o exemplo usará um thread em segundo plano para solicitar que o instalador instale o jogo, enquanto o thread primário está fazendo outra coisa (como reproduzir a sequência de introdução, renderizar o menu ou interagir com o jogador na criação do caractere). A experiência é diferente da instalação tradicional do jogo, na qual o usuário está ocupado no jogo (assistindo à introdução ou criando um novo caractere) enquanto a instalação está em andamento. Depois que o player terminar de criar um caractere, o exemplo carregará os recursos para o nível 1.
No lado direito da tela de exemplo estão cinco botões marcados como "Reproduzir Nível 1" até "Reproduzir Nível 5." Esses botões simulam a conclusão do jogador do nível atual e o avanço para o próximo. Quando um desses botões é clicado, uma tela de estatísticas aparece mostrando informações sobre o nível que ele acabou de terminar. O exemplo também leva esse tempo para solicitar que o instalador verifique e instale o próximo nível, caso ainda não esteja instalado. A instalação ocorre enquanto o jogador está lendo a tela de estatísticas, de modo que, quando o usuário clica em OK para entrar no próximo nível, os recursos do nível estão todos instalados e prontos para serem carregados.
Os recursos e componentes do exemplo
Tradicionalmente, os jogos exigem que todos ou a maioria dos arquivos sejam instalados antes da inicialização. Para obter a instalação sob demanda, os recursos do jogo precisam ser modulares; ou seja, o desenvolvedor deve dividir os recursos do aplicativo (gráficos, áudio e assim por diante) em componentes. Cada componente é um conjunto de recursos que podem ser instalados ou removidos como uma unidade. Depois que isso é feito, o desenvolvedor de jogos define um ou mais recursos, normalmente um ou mais por nível ou zona. Cada recurso de um aplicativo especifica um conjunto de componentes necessários para executar esse recurso específico. Quando um aplicativo é instalado, seus recursos podem ser marcados como "instalação" (componentes copiados para o disco rígido local no momento da instalação) ou "anunciados" (componentes copiados para o disco rígido local quando o aplicativo usa posteriormente esse recurso). Um desenvolvedor de jogos pode reduzir o tempo de instalação projetando o jogo para iniciar e começar a executar com um conjunto mínimo de recursos instalados. O restante de seus recursos pode ser marcado como anunciado e instalado sob demanda quando o aplicativo realmente precisa usar as funcionalidades que esses recursos fornecem.
A tabela a seguir lista os seis recursos de nível superior definidos pelo exemplo.
Nome do recurso | Funcionalidade | Componentes | Arquivos |
---|---|---|---|
Núcleo | Inclui recursos necessários em todos os momentos, independentemente do nível. Esses recursos são: executável de exemplo, mídia exigida pela sequência de introdução e tela de carregamento e arquivo .fx que manipula toda a renderização no exemplo. | Núcleo | InstallOnDemand.exe, InstallOnDemand.fx, Loading.bmp, Level.x |
Núcleo | (o mesmo que acima) | CoreUI | Media\UI\dxutcontrols.dds, Media\UI\DXUTShared.fx, Media\UI\arrow.x |
Núcleo | (o mesmo que acima) | CoreMisc | Media\Misc\seafloor.x, Media\Misc\seafloor.bmp |
Núcleo | (o mesmo que acima) | CoreSpeeder | Demonstração de Media\PRT\LandShark.x, Demonstração media\PRT\speeder_diff.jpg |
Núcleo | (o mesmo que acima) | CoreReg | N/A (valor do registro) |
Level1 | Fornece recursos usados pelo nível 1. | Level1 | Level1.jpg |
Level1 | (igual ao anterior) | L1Skybox | Media\Light Probes\galileo_cross.dds |
Level2 | Fornece recursos usados pelo nível 2. | Level2 | Level2.jpg |
Level2 | (igual ao anterior) | L2Skybox | Media\Light Probes\grace_cross.dds |
Level3 | Fornece recursos usados pelo nível 3. | Level3 | Level3.jpg |
Level3 | (igual ao anterior) | L3Skybox | Media\Light Probes\rnl_cross.dds |
Nível4 | Fornece recursos usados pelo nível 4. | Nível4 | Level4.jpg |
Nível4 | (igual ao anterior) | L4Skybox | Media\Light Probes\stpeters_cross.dds |
Level5 | Fornece recursos usados pelo nível 5. | Level5 | Level5.jpg |
Level5 | (igual ao anterior) | L5Skybox | Media\Light Probes\uffizi_cross.dds |
Os recursos de Nível1 a Nível5 têm sub-recursos adicionais que contêm arquivos não usados diretamente pelo exemplo. Esses arquivos de sub-recursos foram adicionados para que a instalação demorou mais. Isso é feito para ilustrar a operação de instalação em andamento em execução em segundo plano enquanto o exemplo está em execução.
A tabela a seguir lista os sub-recursos.
Recurso | Sub-recursos | Arquivos |
---|---|---|
Level1 | L1PH1, L1PH2, L1PH3, L1PH4, L1PH5 | Dados do espaço reservado Level1\L1PH1.dat Level1 Placeholder Data\L1PH2.dat Level1 Placeholder Data\L1PH3.dat Level1 Placeholder Data\L1PH4.dat Level1 Placeholder Data\L1PH5.dat |
Level2 | L2PH1, L2PH2, L2PH3, L2PH4, L2PH5 | Dados do espaço reservado Level2\L2PH1.dat Level2 Placeholder Data\L2PH2.dat Level2 Placeholder Data\L2PH3.dat Level2 Placeholder Data\L2PH4.dat Level2 Placeholder Data\L2PH5.dat |
Level3 | L3PH1, L3PH2, L3PH3, L3PH4, L3PH5 | Dados do espaço reservado Level3\L3PH1.dat Level3 Placeholder Data\L3PH2.dat Level3 Placeholder Data\L3PH3.dat Level3 Placeholder Data\L3PH4.dat Level3 Placeholder Data\L3PH5.dat |
Nível4 | L4PH1, L4PH2, L4PH3, L4PH4, L4PH5 | Dados do espaço reservado Level4\L4PH1.dat Level4 Placeholder Data\L4PH2.dat Level4 Placeholder Data\L4PH3.dat Level4 Placeholder Data\L4PH4.dat Level4 Placeholder Data\L4PH5.dat |
Level5 | L5PH1, L5PH2, L5PH3, L5PH4, L5PH5 | Dados do espaço reservado Level5\L5PH1.dat Level5 Placeholder Data\L5PH2.dat Level5 Placeholder Data\L5PH3.dat Level5 Placeholder Data\L5PH4.dat Level5 Placeholder Data\L5PH5.dat |
Durante a instalação, o recurso principal deve ser marcado como "instalação" e todos os outros recursos devem ser marcados como "anunciados". Instalando apenas um recurso em vez de seis, o tempo que o jogador deve aguardar até que o jogo seja iniciado é significativamente reduzido.
Instalação
O Windows Installer fornece um mecanismo para um aplicativo solicitar que um recurso anunciado seja instalado. No entanto, o mecanismo é uma chamada de API (interface de programação de aplicativo) síncrona, o que significa que o aplicativo precisa aguardar dentro da chamada até que a instalação seja concluída. Para obter a instalação em segundo plano, um thread de trabalho é necessário para que o thread do aplicativo principal seja livre para executar outras tarefas importantes, como renderizar na tela para continuar fornecendo comentários visuais ao player.
No exemplo, há três estados de instalação que ocorrem durante a execução de exemplo: instalação ativa, instalação passiva e nenhuma instalação.
- A instalação ativa é uma solicitação iniciada pelo exemplo quando precisa acessar ou carregar recursos fornecidos por um ou mais recursos. O exemplo faz isso quando não pode continuar até que o recurso seja instalado.
- A instalação passiva é iniciada quando o exemplo não está executando uma tarefa crítica, como quando o player está em um menu ou assistindo a uma cena de corte. Quando esse é o caso, o thread de trabalho verifica se algum recurso da amostra ainda é anunciado. Se ele puder encontrar um, ele chamará o instalador para instalar esse recurso. Esse processo se repete até que todos os recursos do exemplo sejam instalados. Essencialmente, a instalação passiva utiliza ciclos de processador extras para executar a instalação em segundo plano quando ela é menos intrusiva para o exemplo principal.
- Nenhuma instalação ocorre quando o jogador está ativamente envolvido no jogo; isso impede uma queda de taxa de quadros que interromperia a experiência do usuário.
No exemplo, uma classe CMsiUtil é definida para lidar com todas as tarefas relacionadas à instalação. Essencialmente, o CMsiUtil usa um thread de trabalho que invoca o instalador para instalar os recursos do exemplo em um loop. A classe tem duas filas que armazenam solicitações de instalação: uma fila de alta prioridade para instalação ativa e uma fila de baixa prioridade para instalação passiva. Durante a inicialização, a classe enumera todos os recursos do produto e os adiciona à fila de instalação passiva. Como todo o produto é enfileirado dessa forma, todo o produto eventualmente será instalado se a amostra tiver ciclos de processador gratuitos suficientes.
Quando o exemplo precisa solicitar uma instalação ativa, o exemplo pode chamar CMsiUtil::UseFeatureSet() e passar o nome do recurso de nível superior. UseFeatureSet() enfileirará o recurso solicitado e todos os seus sub-recursos na fila de instalação ativa para que o thread de trabalho possa instalá-los.
Durante a execução de uma solicitação de instalação, o thread de trabalho verificará a fila de instalação ativa, bem como a fila de instalação passiva para ver se qualquer fila tem solicitações adicionais. Sempre que o thread encontrar uma solicitação, ele chamará a API do instalador para executar a instalação real. Depois que ambas as filas estiverem vazias, o thread de trabalho entrará em suspensão com uma chamada para WaitForSingleObject. Como todo o produto é colocado na fila de instalação passiva durante a inicialização, uma fila vazia implica que todo o produto foi instalado.
O exemplo chama CMsiUtil::EnablePassiveInstall() para habilitar ou desabilitar a instalação passiva. EnablePassiveInstall(true) incrementa a contagem de habilitação para instalação passiva e EnablePassiveInstall(false) a diminui. Se a contagem de habilitação for maior que 0, a classe processará a fila de instalação passiva. O exemplo permite a instalação passiva quando qualquer uma das seguintes opções é verdadeira:
- O usuário está exibindo a sequência de introdução inicial.
- O usuário está navegando no menu de exemplo.
- O usuário está exibindo as estatísticas no final de um nível.
- O aplicativo de exemplo perde o foco e vai para o segundo plano.
Os métodos do CMsiUtil estão listados abaixo:
Método | Descrição |
---|---|
AbortAllRequests | Faz com que a instalação atual anule e esvazie a fila de solicitação de instalação ativa. |
AbortCurrentRequest | Faz com que a instalação em andamento seja anulada. O thread de trabalho processa a próxima solicitação na fila, se houver uma. |
EnablePassiveInstall | Incrementa ou decrementa a contagem de habilitação de instalação passiva. O exemplo usa essa chamada para controlar quando a instalação passiva pode e não pode acontecer. |
GetCurrentFeatureName | Retorna o nome do recurso que está sendo instalado ativamente. |
GetFeatureProgress | Retorna a posição de escala atual para o recurso que está sendo instalado. |
GetFeatureProgressMax | Retorna o número máximo de escala de progresso para o recurso que está sendo instalado. |
Obter Último Erro | Use esse método para recuperar o código de retorno da solicitação de instalação anterior. |
GetPassiveProgress | Retorna a posição de escala da barra de progresso para instalação passiva. |
GetPassiveProgressMax | Retorna a posição atual do tique e o número máximo de tiques para instalação passiva. Juntos, o exemplo pode usá-los para mostrar o progresso geral da instalação passiva. |
GetProgress | Retorna a posição de escala da barra de progresso para a instalação do conjunto de recursos ativo. Isso é usado quando o exemplo renderiza a barra de progresso da instalação. Como o Windows Installer fornece apenas informações de progresso para o único recurso que está sendo instalado, o método divide a barra de progresso entre os recursos solicitados para que o usuário ainda veja toda a instalação como uma tarefa. |
GetProgressMax | Retorna a contagem máxima de tiques da barra de progresso para a instalação do conjunto de recursos ativo. Isso é usado quando o exemplo renderiza a barra de progresso da instalação. |
Inicializar | Inicializa a classe com o GUID (identificador global exclusivo) do produto. Esse método também enumera todos os recursos do aplicativo que foram anunciados, mas ainda não instalados, e o coloca na fila de instalação passiva para configurar a instalação passiva. |
IsInstallInProgress | Use esse método para descobrir se uma instalação ativa está sendo processada. |
UseFeature | Método privado chamado por UseFeatureSet. Verifica se um recurso está instalado. Se o recurso solicitado estiver instalado, o método retornará. Se o recurso ainda não estiver instalado (anunciado), o método enfileira uma nova solicitação de instalação ativa para o thread de trabalho e retorna. O identificador de evento opcional que será sinalizado quando a instalação solicitada for concluída. |
UseFeatureSet | Chamado pelo exemplo quando ele precisa acessar as funcionalidades fornecidas por um recurso específico ou qualquer um de seus sub-recursos. O método enumera todos os recursos do exemplo e chama UseFeature() para sub-recursos do recurso raiz especificado. O exemplo pode passar um identificador de evento que será sinalizado quando todo o conjunto de recursos for instalado. Como todos os recursos instalados como um conjunto, o identificador é especificado para o último recurso enfileirado por UseFeature() em vez de todos os recursos, para que o exemplo seja notificado uma vez depois que todos os recursos solicitados forem instalados. |
UseProduct | Enfileira uma solicitação de instalação ativa para que o thread de trabalho chame o instalador para executar uma instalação completa do produto. O identificador de evento opcional que será sinalizado quando a instalação solicitada for concluída. |
Limitação
A versão atual do instalador não foi projetada para acesso simultâneo por vários threads. Portanto, quando o thread de trabalho está chamando o instalador, o thread principal não deve chamar o instalador. Um exemplo dessa limitação ocorre no exemplo quando o thread principal solicita um recurso e solicita o mesmo recurso novamente antes que o thread de trabalho conclua a instalação. A segunda solicitação chama MsiQueryFeatureState() para descobrir se o recurso solicitado já está instalado, já que o instalador pode, às vezes, indicar que o recurso está completamente instalado quando o thread de trabalho ainda está copiando os arquivos.
Felizmente, há uma solução fácil para isso. O CMsiUtil verificará se um recurso está sendo instalado pelo thread de trabalho antes de chamar funções como MsiQueryFeatureState() ou MsiUseFeature() para solicitar o estado de instalação do recurso em questão. Lembre-se de que essa limitação também pode se tornar um problema em outro lugar.
A aplicação de patch pode afetar o funcionamento da instalação sob demanda no computador de um usuário final. A aplicação de um patch que contém apenas dados que foram alterados da versão anterior pode exigir que a versão anterior do arquivo atualizado seja instalada para aplicar o delta. Nessa situação, o patch deve solicitar que o instalador instale os recursos anunciados afetados antes de aplicar o patch ao jogo.
O exemplo é instalado iniciando InstallOnDemand.msi porque pressupõe que o Windows Installer esteja presente no computador. Se o instalador estiver ausente, os arquivos .msi não serão reconhecidos e iniciá-los não funcionará. Para superar esse problema, um aplicativo deve usar um programa de instalação para executar a tarefa. Este programa deve primeiro verificar se o instalador está presente e, se for o caso, sua versão. Se a versão não atender aos requisitos do aplicativo, o programa de instalação deverá instalar o Windows Installer e, em seguida, iniciar o arquivo .msi. Esse processo é conhecido como inicialização. Os aplicativos normalmente nomeia o programa de instalação de inicialização Setup.exe. O exemplo não lida com inicialização. No entanto, detalhes completos sobre inicialização podem ser encontrados no Windows Installer.
Os desenvolvedores também devem prestar atenção ao tamanho de cada recurso em seus jogos. Ao cancelar uma instalação em andamento, o instalador restaura o computador para o estado antes da instalação. Isso significa que a instalação do recurso está completamente desfeita e não há nenhuma instalação parcial do recurso. Um recurso grande requer mais tempo de instalação, o que aumenta a probabilidade de a instalação ser interrompida e cancelada ou a instalação interferirá no aplicativo principal. Um exemplo seria habilitar a instalação passiva quando o usuário abrir o menu do jogo no meio do jogo. Se um recurso estiver sendo instalado e o usuário voltar a jogar, o jogo poderá fazer uma das duas coisas: ele pode permitir que a instalação passiva seja concluída ou cancelar a instalação passiva. Um recurso grande se ajustaria mal a qualquer modelo. Se o jogo permitir que a instalação grande seja concluída, a instalação poderá dificultar o desempenho de renderização do jogo por um longo tempo. Por outro lado, se o jogo cancelar a instalação, o usuário deverá permanecer no menu por muito tempo antes de retornar ao jogo. Os desenvolvedores devem encontrar um tamanho de recurso equilibrado que funcione melhor para seus jogos individuais.