Pular para o conteúdo principal

ValkarnTaskSettings

ValkarnTaskSettings é um ScriptableObject Unity que controla o comportamento em runtime do Valkarn Tasks — principalmente o pool de objetos que recicla máquinas de estados async e objetos promise para evitar garbage durante o jogo.


Criando o Asset de Configurações

  1. Na janela Project, clique com o botão direito em uma pasta Resources (crie uma se não tiver).
  2. Selecione Assets > Create > Valkarn Tasks > Task Settings.
  3. Nomeie o arquivo ValkarnTaskSettings e coloque-o dentro da pasta Resources.

O arquivo deve ser nomeado exatamente ValkarnTaskSettings e deve residir em uma pasta chamada Resources em qualquer lugar do seu projeto. O asset é carregado em runtime com Resources.Load<ValkarnTaskSettings>("ValkarnTaskSettings").

Se nenhum asset for encontrado, todas as configurações voltam para seus padrões integrados. A biblioteca funciona corretamente sem o asset — criá-lo só é necessário quando você quer alterar os padrões.


Acessando as Configurações em Runtime

Em builds Unity, as configurações são lidas do asset por meio de um singleton em cache:

ValkarnTaskSettings settings = ValkarnTaskSettings.Instance;

Os parâmetros de pool mais comuns também são expostos diretamente em ValkarnTask por conveniência:

int max      = ValkarnTask.DefaultMaxPoolSize;   // lê de ValkarnTaskSettings.Instance
int min = ValkarnTask.MinPoolSize;
int interval = ValkarnTask.TrimCheckInterval;

Em builds não-Unity (testes, .NET standalone), ValkarnTaskSettings é uma classe estática com propriedades mutáveis em vez de um ScriptableObject. Os mesmos nomes de propriedade se aplicam e podem ser escritos diretamente:

// Apenas builds não-Unity / de teste
ValkarnTask.DefaultMaxPoolSize = 512;
ValkarnTask.TrimCheckInterval = 600;
ValkarnTask.MinPoolSize = 16;

Propriedades Configuráveis

Configuração do Pool

DefaultMaxPoolSize

Tipoint
Padrão256
Intervalo válido81024
Tooltip do inspetor"Máximo de itens por tipo de pool. Itens em excesso são descartados."

O número máximo de objetos retidos em cada pool por tipo. Cada instanciação genérica distinta (ex.: PooledPromise<int>, PooledPromise<string>) tem seu próprio pool limitado a este valor.

Quando uma task é concluída e seu objeto interno é retornado ao pool, se o pool já mantém DefaultMaxPoolSize itens, o objeto retornado é descartado (elegível para GC). Isso evita crescimento ilimitado de memória após um pico de atividade assíncrona.

Aumente este valor se o profiling mostrar alocações frequentes de GC durante cargas de trabalho assíncronas de alta taxa sustentada. Diminua-o se a pressão de memória for uma preocupação e as tasks não forem reutilizadas com frequência.

MinPoolSize

Tipoint
Padrão8
Intervalo válido164
Tooltip do inspetor"Tamanho mínimo do pool — nunca encolher abaixo disso."

A passagem de trimming do pool nunca reduzirá nenhum pool abaixo desta contagem. Isso garante que uma linha de base de pool aquecido esteja sempre disponível, evitando picos de alocação após um período de silêncio onde a passagem de trimming poderia ter liberado tudo.

TrimCheckInterval

Tipoint
Padrão300
Intervalo válido301000
Tooltip do inspetor"Frames entre verificações de trimming. A 60fps, 300 ≈ 5 segundos."

Quantos frames decorrem entre as passagens de trimming do pool. A passagem de trimming percorre todos os pools registrados e libera objetos em excesso (aqueles acima de MinPoolSize) se o pool tiver sido consistentemente superdimensionado.

A 60 fps, o valor padrão de 300 equivale a aproximadamente 5 segundos entre verificações. Diminua este valor se quiser trimming mais agressivo e frequente (ao custo de mais trabalho de trimming frequente). Aumente-o se as passagens de trimming estiverem aparecendo como picos no profiling.

TrimHysteresisCount

Tipoint
Padrão2
Intervalo válido110
Tooltip do inspetor"Número de verificações consecutivas acima do limiar antes do trimming."

Um pool só é trimado após ter sido observado como superdimensionado por este número de ciclos de trimming consecutivos. Isso evita instabilidade — se o jogo tem um breve pico seguido de um período de silêncio, uma contagem de histerese de 2 significa que o pool sobrevive a um ciclo de silêncio antes de começar a liberar objetos.

TrimReleaseRatio

Tipofloat
Padrão0.25
Intervalo válido0.11.0
Tooltip do inspetor"Fração do excesso a liberar por ciclo de trimming (0.25 = 25%)."

Quando um pool é trimado, esta fração da capacidade excedente (itens acima de MinPoolSize) é liberada por ciclo em vez de tudo de uma vez. Um valor de 0.25 significa que cada passagem de trimming remove 25% do excedente. Esta liberação gradual evita uma queda repentina no tamanho do pool que poderia causar um pico de alocação se a carga aumentar novamente.

Defina como 1.0 se quiser todo o excesso liberado imediatamente em cada ciclo.


Ciclo de Vida

EnableAutoCancel

Tipobool
Padrãotrue
Tooltip do inspetor"Vincular automaticamente tasks de MonoBehaviour ao destroyCancellationToken."

Quando habilitado, tasks iniciadas a partir de um MonoBehaviour são automaticamente vinculadas ao destroyCancellationToken daquele MonoBehaviour. Se o MonoBehaviour for destruído enquanto uma task está em execução, a task é cancelada em vez de continuar a executar contra um objeto destruído.

Desabilite isso apenas se você estiver gerenciando o cancelamento manualmente e não quiser vínculo automático.


Tratamento de Erros

LogUnobservedCancellations

Tipobool
Padrãofalse
Tooltip do inspetor"Registrar cancelamentos não observados como avisos."

Por padrão, uma task que é cancelada mas nunca aguardada (cancelamento não observado) é silenciosamente ignorada. Habilite isso para registrar um aviso quando isso acontecer. Útil durante o desenvolvimento para encontrar tasks fire-and-forget que cancelam silenciosamente.

Falhas não observadas são sempre relatadas via o evento ValkarnTask.UnobservedException independentemente desta configuração.

MaxExceptionLogsPerFrame

Tipoint
Padrão10
Intervalo válido1100
Tooltip do inspetor"Máximo de logs de exceção por frame para evitar spam."

Limita o número de entradas de log de exceção não observada emitidas em um único frame. Se muitas tasks falharem no mesmo frame (por exemplo, após uma falha de rede), isso evita que o console seja inundado com centenas de rastreamentos de pilha idênticos.


Como o Trimming do Pool Funciona em Runtime

Em cada atualização do player loop do Unity, PlayerLoopHelper incrementa um contador de frames. Quando o contador atinge TrimCheckInterval, chama PoolRegistry.TrimAll(MinPoolSize). Todo pool que foi registrado verifica se tem sido superdimensionado por pelo menos TrimHysteresisCount verificações consecutivas. Se sim, libera TrimReleaseRatio de seu excesso.

Os pools se registram automaticamente no primeiro uso. O método ValkarnTask.GetPoolInfo() retorna um snapshot de todos os pools atualmente registrados com seu tipo, tamanho atual e tamanho máximo — isso é o que a janela Task Tracker exibe.

Frame 0 ──────────────────────────────────────────────────────────
Player loop executa
Pool A: tamanho 200, máx 256 — normal, sem trim necessário

Frame 300 ────────────────────────────────────────────────────────
TrimAll dispara
Pool A: tamanho 18, máx 256 — acima de MinPoolSize(8), mas primeira verificação de excesso
hysteresisCount para A = 1 (ainda não no limiar de 2)

Frame 600 ────────────────────────────────────────────────────────
TrimAll dispara
Pool A: tamanho 18, máx 256 — acima de MinPoolSize(8), segunda verificação de excesso
hysteresisCount para A = 2 — limiar atingido!
Excesso = 18 - 8 = 10; liberar 10 * 0.25 = 2 objetos
Pool A: tamanho 16

Janela Task Tracker

Abra via Window > Valkarn Tasks > Task Tracker.

O Task Tracker é uma janela somente do Editor que mostra o estado ao vivo do pool enquanto você está no Modo de Jogo. Atualiza em um intervalo configurável (padrão de 0,5 segundos, ajustável de 0,1 a 5 segundos via o slider na barra de ferramentas).

Aba Pools

Lista todos os tipos de pool que estiveram ativos desde o último domain reload, classificados por tamanho atual (do maior para o menor). Cada linha mostra:

ColunaDescrição
TipoO nome do tipo do objeto em pool, com argumentos genéricos expandidos
TamanhoNúmero atual de objetos no pool
MáxO teto DefaultMaxPoolSize para este pool
UsoUma barra de progresso mostrando Tamanho / Máx como porcentagem

Se nenhum pool tiver sido usado ainda, uma mensagem diz "Nenhum pool ativo. Os pools são criados no primeiro uso."

Aba Config

Mostra os três parâmetros de pool ao vivo conforme lidos de ValkarnTask.DefaultMaxPoolSize, ValkarnTask.TrimCheckInterval e ValkarnTask.MinPoolSize. Os valores só são mostrados enquanto no Modo de Jogo — no Modo de Edição uma nota instrui você a entrar no Modo de Jogo para ver os valores ao vivo.

A aba Config também exibe uma referência ao asset ValkarnTaskSettings (se um existir em Resources), para que você possa clicar para inspecioná-lo ou modificá-lo. Se nenhum asset for encontrado, um aviso direciona você a criar um.


Result<T>

Result<T> é uma union discriminada em struct para representar o resultado de uma operação sem lançar exceção. É o tipo de retorno usado pelos combinadores WhenAll para relatar resultados por task, e também está disponível como um padrão de resultado de uso geral.

public readonly struct Result<T>
{
public bool IsSuccess { get; }
public bool IsFailure { get; } // true se faulted ou cancelado
public bool IsFaulted { get; }
public bool IsCanceled { get; }

public ValkarnTask.Status Status { get; }
public T Value { get; } // lança se não foi bem-sucedido
public Exception Error { get; } // null se não faulted

public static Result<T> Success(T value);
public static Result<T> Failure(string error); // envolve em InvalidOperationException
public static Result<T> Faulted(Exception error);
public static Result<T> Canceled(OperationCanceledException oce = null);

public static implicit operator bool(Result<T> r); // true se foi bem-sucedido
}

Um Result não-genérico existe para tasks void com a mesma forma mas sem Value.

Métodos de extensão AsResult

Converta qualquer ValkarnTask ou ValkarnTask<T> em um Result sem um try/catch em seu próprio código:

public static ValkarnTask<Result<T>> AsResult<T>(this ValkarnTask<T> task);
public static ValkarnTask<Result> AsResult(this ValkarnTask task);

Se a task subjacente já estiver sincronamente concluída (o caminho rápido de zero alocação), AsResult retorna imediatamente sem criar uma máquina de estados async. Caso contrário, envolve em um método async que captura OperationCanceledException e todas as outras exceções, traduzindo-as na variante Result apropriada.

Quando usar Result<T>

Use Result<T> em vez de capturar exceções quando:

  • Você está chamando múltiplas tasks em paralelo e quer resultados por task sem curto-circuitar o lote inteiro.
  • Quer expressar operações falíveis de forma type-safe sem fluxo de controle por exceção.
  • Está retornando de um método que o chamador pode não querer envolver em try/catch.
// Disparar múltiplas tasks, obter todos os resultados independentemente de falhas individuais
Result<int>[] results = await ValkarnTask.WhenAll(
FetchScoreAsync(playerA).AsResult(),
FetchScoreAsync(playerB).AsResult(),
FetchScoreAsync(playerC).AsResult()
);

foreach (var r in results)
{
if (r.IsSuccess)
Debug.Log($"Pontuação: {r.Value}");
else if (r.IsFaulted)
Debug.LogError($"Falhou: {r.Error.Message}");
else
Debug.Log("Cancelado");
}

Verificar IsSuccess ou usar o operador implícito bool são as formas preferidas de ramificar em um resultado:

var result = await SomeOperationAsync().AsResult();

if (result)
{
Use(result.Value);
}

Acessar result.Value quando IsSuccess é false lança InvalidOperationException.

Métodos factory

MétodoStatus definidoErro definido
Result<T>.Success(value)Succeedednenhum
Result<T>.Failure(message)Faultednew InvalidOperationException(message)
Result<T>.Faulted(exception)Faulteda exceção fornecida
Result<T>.Canceled(oce?)Canceleda OperationCanceledException fornecida, ou null

A propriedade Succeeded existe em Result e Result<T>, mas está marcada como [Obsolete] — use IsSuccess em vez disso, por consistência com IsFailure, IsFaulted e IsCanceled.