Eu tenho um método chamado render
que é chamado em cada quadro para desenhar algo na tela usando Metal. Ele está renderizando uma série de pontos armazenados dentro da classe. No entanto, ocasionalmente preciso atualizar esse conjunto de pontos. Estou lutando para encontrar uma solução que possa atualizar esses pontos de maneira segura para a simultaneidade.
Ou seja, ao render
tentar acessar esse array, outro thread pode estar atualizando-o, causando uma falha. Além disso, render
é síncrono e não pode bloquear ou aguardar a atualização. Este parece ser um problema de simultaneidade comum, por isso estou procurando ajuda aqui para ver quais soluções existem.
Para generalizar o problema - há alguma função síncrona que é chamada com muita frequência, ela precisa de acesso a um dado que pode ser atualizado por outro thread. Eu tenho controle sobre o render
método e o "outro" thread que faz a atualização. Como posso evitar que uma condição de corrida trave o aplicativo? (só não precisa travar, não há considerações de "correção" dos dados)
Uma possível solução que eu hipotetizei é usar atômicos. Não tenho certeza de como isso poderia ser feito, especialmente em Swift (não acha que Swift tem atômicos?)
EDITAR:
Tentarei adicionar mais contexto aqui, mas pode ser difícil adicionar um MRE, considerando que parte do que estou implementando está guardado em um repositório privado ao qual não tenho acesso.
Resumindo, estou tentando implementar uma camada personalizada do MapBox seguindo este exemplo . Os detalhes desta render
função específica no exemplo são irrelevantes, pois meu método de renderização precisa fazer algo completamente diferente, mas seria algo assim em pseudocódigo:
class MyCustomLayer: NSObject, CustomLayerHost {
var locationData: [CLLocation] = []
func render(...) {
// calculate the viewport from the parameters
let viewport = ...
if viewport.fitSomeCondition {
// fetch new location data. This can't block and needs to happen async
fetchLocationDataFromServer(viewport: viewport)
// need to somehow update the locationData
}
// render locationData
}
}
Aqui locationData
está uma matriz de longitudes e latitudes e render
está separada do CustomLayerHost
protocolo do MapBox iOS SDK . O código-fonte de como CustomLayerHost
é tratado não está disponível ao público, mas aqui está a declaração do cabeçalho:
/**
* Render the layer. This method is called once per frame.
*
* This method is called if underlying map rendering backend uses Metal graphic API.
*
* @param The `parameters` that define the current camera position.
* @param The MTLCommandBuffer used to render all map layers.
* Use to create new command encoders. Do not submit as there could be map
* layers following this custom layer in style's layer list and those won't get
* to be encoded.
* @param The MTLRenderPassDescriptor that defines rendering map to view drawable.
*
*/
- (void)render:(nonnull MBMCustomLayerRenderParameters *)parameters
mtlCommandBuffer:(nonnull id<MTLCommandBuffer>)mtlCommandBuffer
mtlRenderPassDescriptor:(nonnull MTLRenderPassDescriptor *)mtlRenderPassDescriptor;
Entendo que este não é um MRE, mas espero que ajude a tornar a questão mais clara.
Se você está apenas tentando evitar a corrida de dados
locationData
, recomendo adicionar alguma sincronização. Por exemplo, em cenários de alto desempenho, podemos optar por uma sincronização baseada em bloqueio:Existem muitos padrões de sincronização (filas seriais GCD, atores, etc.), mas para algo que precisa ser executado tão frequentemente quanto quadro a quadro, eu poderia me inclinar para um mecanismo de sincronização com melhor desempenho, ou seja, bloqueios injustos, mostrados acima .
Observe que queremos minimizar o número de sincronizações, portanto, eu evitaria referenciar
locationData
repetidamente dentro derender
, mas sim fazê-lo uma vez, atribuindo-o a uma variável local.Além disso, está além do escopo da questão, mas pense muito
fitSomeCondition
e evite solicitações de rede redundantes ou simultâneas.Se valer a pena, posso considerar mover essa sincronização baseada em bloqueio para seu próprio wrapper de propriedade:
E isso simplifica
MyCustomLayer
: