Partilhar via


Transferências de âncora local no DirectX

As transferências de âncora local permitem que um dispositivo HoloLens exporte uma âncora a ser importada por um segundo dispositivo HoloLens.

Nota

Os dispositivos iOS e Android não são suportados por esta abordagem.

Nota

Os trechos de código neste artigo atualmente demonstram o uso de C++/CX em vez de C++/WinRT compatível com C++17 como usado no modelo de projeto holográfico C++. Os conceitos são equivalentes para um projeto C++/WinRT, embora você precise traduzir o código.

Transferência de âncoras espaciais

Você pode transferir âncoras espaciais entre dispositivos Windows Mixed Reality usando o SpatialAnchorTransferManager. Essa API permite que você agrupe uma âncora com todos os dados do sensor de suporte necessários para encontrar esse lugar exato no mundo e, em seguida, importe esse pacote em outro dispositivo. Uma vez que o aplicativo no segundo dispositivo tenha importado essa âncora, cada aplicativo pode renderizar hologramas usando o sistema de coordenadas da âncora espacial compartilhada, que aparecerá no mesmo lugar no mundo real.

Observe que as âncoras espaciais não são capazes de transferir entre diferentes tipos de dispositivos, por exemplo, uma âncora espacial HoloLens pode não ser localizável usando um fone de ouvido imersivo. As âncoras transferidas também não são compatíveis com dispositivos iOS ou Android.

Configurar seu aplicativo para usar o recurso spatialPerception

Seu aplicativo deve receber permissão para usar o recurso SpatialPerception antes de poder usar o SpatialAnchorTransferManager. Isso é necessário porque a transferência de uma âncora espacial envolve o compartilhamento de imagens de sensores coletadas ao longo do tempo nas proximidades dessa âncora, que podem incluir informações confidenciais.

Declare esse recurso no arquivo package.appxmanifest do seu aplicativo. Eis um exemplo:

<Capabilities>
  <uap2:Capability Name="spatialPerception" />
</Capabilities>

O recurso vem do namespace uap2 . Para obter acesso a esse namespace em seu manifesto, inclua-o como um atributo xlmns no elemento Package>.< Eis um exemplo:

<Package
    xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
    xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
    xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
    xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
    IgnorableNamespaces="uap mp"
    >

NOTA: Seu aplicativo precisará solicitar o recurso em tempo de execução antes de poder acessar APIs de exportação/importação do SpatialAnchor. Consulte RequestAccessAsync nos exemplos abaixo.

Serialize dados âncora exportando-os com o SpatialAnchorTransferManager

Uma função auxiliar é incluída no exemplo de código para exportar (serializar) dados SpatialAnchor . Essa API de exportação serializa todas as âncoras em uma coleção de pares chave-valor que associam cadeias de caracteres a âncoras.

// ExportAnchorDataAsync: Exports a byte buffer containing all of the anchors in the given collection.
//
// This function will place data in a buffer using a std::vector<byte>. The ata buffer contains one or more
// Anchors if one or more Anchors were successfully imported; otherwise, it is ot modified.
//
task<bool> SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
    vector<byte>* anchorByteDataOut,
    IMap<String^, SpatialAnchor^>^ anchorsToExport
    )
{

Primeiro, precisamos configurar o fluxo de dados. Isto permitir-nos-á 1.) use TryExportAnchorsAsync para colocar os dados em um buffer de propriedade do aplicativo e 2.) ler dados do fluxo de buffer de bytes exportado - que é um fluxo de dados WinRT - em nosso próprio buffer de memória, que é um byte> std::vetor<.

// Create a random access stream to process the anchor byte data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor byte stream.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);

Precisamos pedir permissão para acessar dados espaciais, incluindo âncoras que são exportadas pelo sistema.

// Request access to spatial data.
auto accessRequestedTask = create_taskSpatialAnchorTransferManager::RequestAccessAsync()).then([anchorsToExport, utputStream](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.
        // Export the indicated set of anchors.
        return create_task(SpatialAnchorTransferManager::TryExportAnchorsAsync(
            anchorsToExport,
            outputStream
            ));
    }
    else
    {
        // Access is denied.
        return task_from_result<bool>(false);
    }
});

Se obtivermos permissão e as âncoras forem exportadas, podemos ler o fluxo de dados. Aqui, também mostramos como criar o DataReader e o InputStream que usaremos para ler os dados.

// Get the input stream for the anchor byte stream.
IInputStream^ inputStream = stream->GetInputStreamAt(0);
// Create a DataReader, to get bytes from the anchor byte stream.
DataReader^ reader = ref new DataReader(inputStream);
return accessRequestedTask.then([anchorByteDataOut, stream, reader](bool nchorsExported)
{
    if (anchorsExported)
    {
        // Get the size of the exported anchor byte stream.
        size_t bufferSize = static_cast<size_t>(stream->Size);
        // Resize the output buffer to accept the data from the stream.
        anchorByteDataOut->reserve(bufferSize);
        anchorByteDataOut->resize(bufferSize);
        // Read the exported anchor store into the stream.
        return create_task(reader->LoadAsync(bufferSize));
    }
    else
    {
        return task_from_result<size_t>(0);
    }

Depois de lermos bytes do fluxo, podemos salvá-los em nosso próprio buffer de dados assim.

}).then([anchorByteDataOut, reader](size_t bytesRead)
{
    if (bytesRead > 0)
    {
        // Read the bytes from the stream, into our data output buffer.
        reader->ReadBytes(Platform::ArrayReference<byte>(&(*anchorByteDataOut)[0], bytesRead));
        return true;
    }
    else
    {
        return false;
    }
});
};

Desserialize dados âncora importando-os para o sistema usando o SpatialAnchorTransferManager

Uma função auxiliar é incluída no exemplo de código para carregar dados exportados anteriormente. Essa função de desserialização fornece uma coleção de pares chave-valor, semelhante ao que o SpatialAnchorStore fornece - exceto que obtivemos esses dados de outra fonte, como um soquete de rede. Você pode processar e raciocinar sobre esses dados antes de armazená-los offline, usando a memória no aplicativo ou (se aplicável) a SpatialAnchorStore do seu aplicativo.

// ImportAnchorDataAsync: Imports anchors from a byte buffer that was previously exported.
//
// This function will import all anchors from a data buffer into an in-memory ollection of key, value
// pairs that maps String objects to SpatialAnchor objects. The Spatial nchorStore is not affected by
// this function unless you provide it as the target collection for import.
//
task<bool> SpatialAnchorImportExportHelper::ImportAnchorDataAsync(
    std::vector<byte>& anchorByteDataIn,
    IMap<String^, SpatialAnchor^>^ anchorMapOut
    )
{

Primeiro, precisamos criar objetos de fluxo para acessar os dados de âncora. Estaremos gravando os dados de nosso buffer em um buffer do sistema, então criaremos um DataWriter que grava em um fluxo de dados na memória para atingir nosso objetivo de obter âncoras de um buffer de bytes no sistema como SpatialAnchors.

// Create a random access stream for the anchor data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor data.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);
// Create a writer, to put the bytes in the stream.
DataWriter^ writer = ref new DataWriter(outputStream);

Mais uma vez, precisamos garantir que o aplicativo tenha permissão para exportar dados de âncora espacial, que podem incluir informações privadas sobre o ambiente do usuário.

// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
    [&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
    if (status == SpatialPerceptionAccessStatus::Allowed)
    {
        // Access is allowed.

Se o acesso for permitido, podemos gravar bytes do buffer em um fluxo de dados do sistema.

// Write the bytes to the stream.
        byte* anchorDataFirst = &anchorByteDataIn[0];
        size_t anchorDataSize = anchorByteDataIn.size();
        writer->WriteBytes(Platform::ArrayReference<byte>(anchorDataFirst, anchorDataSize));
        // Store the stream.
        return create_task(writer->StoreAsync());
    }
    else
    {
        // Access is denied.
        return task_from_result<size_t>(0);
    }

Se formos bem-sucedidos no armazenamento de bytes no fluxo de dados, podemos tentar importar esses dados usando o SpatialAnchorTransferManager.

}).then([writer, stream](unsigned int bytesWritten)
{
    if (bytesWritten > 0)
    {
        // Try to import anchors from the byte stream.
        return create_task(writer->FlushAsync())
            .then([stream](bool dataWasFlushed)
        {
            if (dataWasFlushed)
            {
                // Get the input stream for the anchor data.
                IInputStream^ inputStream = stream->GetInputStreamAt(0);
                return create_task(SpatialAnchorTransferManager::TryImportAnchorsAsync(inputStream));
            }
            else
            {
                return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
            }
        });
    }
    else
    {
        return task_from_result<IMapView<String^, SpatialAnchor^>^>(nullptr);
    }

Se os dados puderem ser importados, obteremos uma visualização de mapa de pares chave-valor associando cadeias de caracteres a âncoras. Podemos carregar isso em nossa própria coleção de dados na memória e usar essa coleção para procurar âncoras que estamos interessados em usar.

}).then([anchorMapOut](task<Windows::Foundation::Collections::IMapView<String^, SpatialAnchor^>^>  previousTask)
{
    try
    {
        auto importedAnchorsMap = previousTask.get();
        // If the operation was successful, we get a set of imported anchors.
        if (importedAnchorsMap != nullptr)
        {
            for each (auto& pair in importedAnchorsMap)
            {
                // Note that you could look for specific anchors here, if you know their key values.
                auto const& id = pair->Key;
                auto const& anchor = pair->Value;
                // Append "Remote" to the end of the anchor name for disambiguation.
                std::wstring idRemote(id->Data());
                idRemote += L"Remote";
                String^ idRemoteConst = ref new String (idRemote.c_str());
                // Store the anchor in the current in-memory anchor map.
                anchorMapOut->Insert(idRemoteConst, anchor);
            }
            return true;
        }
    }
    catch (Exception^ exception)
    {
        OutputDebugString(L"Error: Unable to import the anchor data buffer bytes into the in-memory anchor collection.\n");
    }
    return false;
});
}

NOTA: Só porque você pode importar uma âncora, não significa necessariamente que você pode usá-lo imediatamente. A âncora pode estar em uma sala diferente, ou em outro local físico inteiramente; A âncora não será localizável até que o dispositivo que a recebeu tenha informações visuais suficientes sobre o ambiente em que a âncora foi criada, para restaurar a posição da âncora em relação ao ambiente atual conhecido. A implementação do cliente deve tentar localizar a âncora relativa ao seu sistema de coordenadas local ou quadro de referência antes de continuar para tentar usá-la para conteúdo ao vivo. Por exemplo, tente localizar a âncora em relação a um sistema de coordenadas atual periodicamente até que a âncora comece a ser localizável.

Considerações especiais

A API TryExportAnchorsAsync permite que vários SpatialAnchors sejam exportados para o mesmo blob binário opaco. No entanto, há uma diferença sutil em quais dados o blob incluirá, dependendo se um único SpatialAnchor ou vários SpatialAnchors são exportados em uma única chamada.

Exportação de um único SpatialAnchor

O blob contém uma representação do ambiente nas proximidades do SpatialAnchor para que o ambiente possa ser reconhecido no dispositivo que importa o SpatialAnchor. Após a conclusão da importação, o novo SpatialAnchor estará disponível para o dispositivo. Supondo que o usuário tenha estado recentemente nas proximidades da âncora, ela será localizável e hologramas anexados à SpatialAnchor podem ser renderizados. Esses hologramas aparecerão no mesmo local físico que no dispositivo original que exportou o SpatialAnchor.

Exportação de um único SpatialAnchor

Exportação de múltiplos SpatialAnchors

Como a exportação de um único SpatialAnchor, o blob contém uma representação do ambiente na vizinhança de todos os SpatialAnchors especificados. Além disso, o blob contém informações sobre as conexões entre os SpatialAnchors incluídos, se eles estiverem localizados no mesmo espaço físico. Isso significa que, se duas SpatialAnchors próximas forem importadas, um holograma anexado à segunda SpatialAnchor será localizável, mesmo que o dispositivo reconheça apenas o ambiente em torno da primeira SpatialAnchor, porque dados suficientes para calcular a transformação entre as duas SpatialAnchors foram incluídos no blob. Se os dois SpatialAnchors foram exportados individualmente (duas chamadas separadas para TryExportSpatialAnchors), então pode não haver dados suficientes incluídos no blob para hologramas anexados ao segundo SpatialAnchor para serem localizáveis quando o primeiro estiver localizado.

Várias âncoras exportadas usando uma única chamada TryExportAnchorsAsync Várias âncoras exportadas usando uma chamada TryExportAnchorsAsync separada para cada âncora

Exemplo: Enviar dados de âncora usando um Windows::Networking::StreamSocket

Aqui, fornecemos um exemplo de como usar dados âncora exportados enviando-os através de uma rede TCP. Isso é do HolographicSpatialAnchorTransferSample.

A classe WinRT StreamSocket usa a biblioteca de tarefas PPL. No caso de erros de rede, o erro é retornado para a próxima tarefa na cadeia usando uma exceção que é relançada. A exceção contém um HRESULT indicando o status do erro.

Use um Windows::Networking::StreamSocketListener com TCP para enviar dados âncora exportados

Crie uma instância de servidor que escuta uma conexão.

void SampleAnchorTcpServer::ListenForConnection()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocketListener^ streamSocketListener = m_socketServer;
    if (streamSocketListener == nullptr)
    {
        OutputDebugString(L"Server listening for client.\n");
        // Create the web socket connection.
        streamSocketListener = ref new StreamSocketListener();
        streamSocketListener->Control->KeepAlive = true;
        streamSocketListener->BindEndpointAsync(
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        streamSocketListener->ConnectionReceived +=
            ref new Windows::Foundation::TypedEventHandler<StreamSocketListener^, StreamSocketListenerConnectionReceivedEventArgs^>(
                std::bind(&SampleAnchorTcpServer::OnConnectionReceived, this, _1, _2)
                );
        m_socketServer = streamSocketListener;
    }
    else
    {
        OutputDebugString(L"Error: Stream socket listener not created.\n");
    }
}

Quando uma conexão for recebida, use a conexão de soquete do cliente para enviar dados de âncora.

void SampleAnchorTcpServer::OnConnectionReceived(StreamSocketListener^ listener, StreamSocketListenerConnectionReceivedEventArgs^ args)
{
    m_socketForClient = args->Socket;
    if (m_socketForClient != nullptr)
    {
        // In this example, when the client first connects, we catch it up to the current state of our anchor set.
        OutputToClientSocket(m_spatialAnchorHelper->GetAnchorMap());
    }
}

Agora, podemos começar a enviar um fluxo de dados que contém os dados âncora exportados.

void SampleAnchorTcpServer::OutputToClientSocket(IMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    m_anchorTcpSocketStreamWriter = ref new DataWriter(m_socketForClient->OutputStream);
    OutputDebugString(L"Sending stream to client.\n");
    SendAnchorDataStream(anchorsToSend).then([this](task<bool> previousTask)
    {
        try
        {
            bool success = previousTask.get();
            if (success)
            {
                OutputDebugString(L"Anchor data sent!\n");
            }
            else
            {
                OutputDebugString(L"Error: Anchor data not sent.\n");
            }
        }
        catch (Exception^ exception)
        {
            HandleException(exception);
            OutputDebugString(L"Error: Anchor data was not sent.\n");
        }
    });
}

Antes de podermos enviar o fluxo em si, devemos primeiro enviar um pacote de cabeçalho. Este pacote de cabeçalho deve ser de comprimento fixo e também deve indicar o comprimento da matriz variável de bytes que é o fluxo de dados âncora; No caso deste exemplo, não temos outros dados de cabeçalho para enviar, portanto, nosso cabeçalho tem 4 bytes de comprimento e contém um inteiro não assinado de 32 bits.

Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataLengthMessage(size_t dataStreamLength)
{
    unsigned int arrayLength = dataStreamLength;
    byte* data = reinterpret_cast<byte*>(&arrayLength);
    m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    return create_task(m_anchorTcpSocketStreamWriter->StoreAsync()).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            OutputDebugString(L"Anchor data length stored in stream; Flushing stream.\n");
            return create_task(m_anchorTcpSocketStreamWriter->FlushAsync());
        }
        else
        {
            OutputDebugString(L"Error: Anchor data length not stored in stream.\n");
            return task_from_result<bool>(false);
        }
    });
}
Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataStreamIMap<String^, SpatialAnchor^>^ anchorsToSend)
{
    return SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
        &m_exportedAnchorStoreBytes,
        anchorsToSend
        ).then([this](bool anchorDataExported)
    {
        if (anchorDataExported)
        {
            const size_t arrayLength = m_exportedAnchorStoreBytes.size();
            if (arrayLength > 0)
            {
                OutputDebugString(L"Anchor data was exported; sending data stream length message.\n");
                return SendAnchorDataLengthMessage(arrayLength);
            }
        }
        OutputDebugString(L"Error: Anchor data was not exported.\n");
        // No data to send.
        return task_from_result<bool>(false);

Uma vez que o comprimento do fluxo, em bytes, foi enviado para o cliente, podemos continuar a gravar o fluxo de dados em si para o fluxo de soquete. Isso fará com que os bytes do armazenamento âncora sejam enviados para o cliente.

}).then([this](bool dataLengthSent)
    {
        if (dataLengthSent)
        {
            OutputDebugString(L"Data stream length message sent; writing exported anchor store bytes to stream.\n");
            m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0], m_exportedAnchorStoreBytes.size()));
            return create_task(m_anchorTcpSocketStreamWriter->StoreAsync());
        }
        else
        {
            OutputDebugString(L"Error: Data stream length message not sent.\n");
            return task_from_result<size_t>(0);
        }
    }).then([this](unsigned int bytesStored)
    {
        if (bytesStored > 0)
        {
            PrintWstringToDebugConsole(
                std::to_wstring(bytesStored) +
                L" bytes of anchor data written and stored to stream; flushing stream.\n"
                );
        }
        else
        {
            OutputDebugString(L"Error: No anchor data bytes were written to the stream.\n");
        }
        return task_from_result<bool>(false);
    });
}

Como observado anteriormente neste tópico, devemos estar preparados para lidar com exceções que contenham mensagens de status de erro de rede. Para erros que não são esperados, podemos gravar as informações de exceção no console de depuração assim. Isso nos dará uma pista sobre o que aconteceu se nosso exemplo de código não conseguir concluir a conexão ou se não conseguir concluir o envio dos dados da âncora.

void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
    PrintWstringToDebugConsole(
        std::wstring(L"Connection error: ") +
        exception->ToString()->Data() +
        L"\n"
        );
}

Use um Windows::Networking::StreamSocket com TCP para receber dados âncora exportados

Primeiro, temos que nos conectar ao servidor. Este exemplo de código mostra como criar e configurar um StreamSocket e criar um DataReader que você pode usar para adquirir dados de rede usando a conexão de soquete.

Observação : se você executar este código de exemplo, certifique-se de configurar e iniciar o servidor antes de iniciar o cliente.

task<bool> SampleAnchorTcpClient::ConnectToServer()
{
    // Make a local copy to avoid races with Closed events.
    StreamSocket^ streamSocket = m_socketClient;
    // Have we connected yet?
    if (m_socketClient == nullptr)
    {
        OutputDebugString(L"Client is attempting to connect to server.\n");
        EndpointPair^ endpointPair = ref new EndpointPair(
            SampleAnchorTcpCommon::m_clientHost,
            SampleAnchorTcpCommon::m_tcpPort,
            SampleAnchorTcpCommon::m_serverHost,
            SampleAnchorTcpCommon::m_tcpPort
            );
        // Create the web socket connection.
        m_socketClient = ref new StreamSocket();
        // The client connects to the server.
        return create_task(m_socketClient->ConnectAsync(endpointPair, SocketProtectionLevel::PlainSocket)).then([this](task<void> previousTask)
        {
            try
            {
                // Try getting all exceptions from the continuation chain above this point.
                previousTask.get();
                m_anchorTcpSocketStreamReader = ref new DataReader(m_socketClient->InputStream);
                OutputDebugString(L"Client connected!\n");
                m_anchorTcpSocketStreamReader->InputStreamOptions = InputStreamOptions::ReadAhead;
                WaitForAnchorDataStream();
                return true;
            }
            catch (Exception^ exception)
            {
                if (exception->HResult == 0x80072741)
                {
                    // This code sample includes a very simple implementation of client/server
                    // endpoint detection: if the current instance tries to connect to itself,
                    // it is determined to be the server.
                    OutputDebugString(L"Starting up the server instance.\n");
                    // When we return false, we'll start up the server instead.
                    return false;
                }
                else if ((exception->HResult == 0x8007274c) || // connection timed out
                    (exception->HResult == 0x80072740)) // connection maxed at server end
                {
                    // If the connection timed out, try again.
                    ConnectToServer();
                }
                else if (exception->HResult == 0x80072741)
                {
                    // No connection is possible.
                }
                HandleException(exception);
                return true;
            }
        });
    }
    else
    {
        OutputDebugString(L"A StreamSocket connection to a server already exists.\n");
        return task_from_result<bool>(true);
    }
}

Uma vez que tenhamos uma conexão, podemos esperar que o servidor envie dados. Fazemos isso chamando LoadAsync no leitor de dados de fluxo.

O primeiro conjunto de bytes que recebemos deve ser sempre o pacote de cabeçalho, que indica o comprimento do byte do fluxo de dados âncora, conforme descrito na seção anterior.

void SampleAnchorTcpClient::WaitForAnchorDataStream()
{
    if (m_anchorTcpSocketStreamReader == nullptr)
    {
        // We have not connected yet.
        return;
    }
    OutputDebugString(L"Waiting for server message.\n");
    // Wait for the first message, which specifies the byte length of the string data.
    create_task(m_anchorTcpSocketStreamReader->LoadAsync(SampleAnchorTcpCommon::c_streamHeaderByteArrayLength)).then([this](unsigned int numberOfBytes)
    {
        if (numberOfBytes > 0)
        {
            OutputDebugString(L"Server message incoming.\n");
            return ReceiveAnchorDataLengthMessage();
        }
        else
        {
            OutputDebugString(L"0-byte async task received, awaiting server message again.\n");
            WaitForAnchorDataStream();
            return task_from_result<size_t>(0);
        }

...

task<size_t> SampleAnchorTcpClient::ReceiveAnchorDataLengthMessage()
{
    byte data[4];
    m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
    unsigned int lengthMessageSize = *reinterpret_cast<unsigned int*>(data);
    if (lengthMessageSize > 0)
    {
        OutputDebugString(L"One or more anchors to be received.\n");
        return task_from_result<size_t>(lengthMessageSize);
    }
    else
    {
        OutputDebugString(L"No anchors to be received.\n");
        ConnectToServer();
    }
    return task_from_result<size_t>(0);
}

Depois de recebermos o pacote de cabeçalho, sabemos quantos bytes de dados de âncora devemos esperar. Podemos continuar a ler esses bytes do fluxo.

}).then([this](size_t dataStreamLength)
    {
        if (dataStreamLength > 0)
        {
            std::wstring debugMessage = std::to_wstring(dataStreamLength);
            debugMessage += L" bytes of anchor data incoming.\n";
            OutputDebugString(debugMessage.c_str());
            // Prepare to receive the data stream in one or more pieces.
            m_anchorStreamLength = dataStreamLength;
            m_exportedAnchorStoreBytes.clear();
            m_exportedAnchorStoreBytes.resize(m_anchorStreamLength);
            OutputDebugString(L"Loading byte stream.\n");
            return ReceiveAnchorDataStream();
        }
        else
        {
            OutputDebugString(L"Error: Anchor data size not received.\n");
            ConnectToServer();
            return task_from_result<bool>(false);
        }
    });
}

Aqui está o nosso código para receber o fluxo de dados âncora. Novamente, primeiro carregaremos os bytes do fluxo; essa operação pode levar algum tempo para ser concluída, pois o StreamSocket aguarda para receber essa quantidade de bytes da rede.

Quando a operação de carregamento estiver concluída, podemos ler esse número de bytes. Se recebemos o número de bytes que esperamos para o fluxo de dados âncora, podemos ir em frente e importar os dados da âncora; se não, deve ter havido algum tipo de erro. Por exemplo, isso pode acontecer quando a instância do servidor termina antes de concluir o envio do fluxo de dados ou a rede fica inativa antes que todo o fluxo de dados possa ser recebido pelo cliente.

task<bool> SampleAnchorTcpClient::ReceiveAnchorDataStream()
{
    if (m_anchorStreamLength > 0)
    {
        // First, we load the bytes from the network socket.
        return create_task(m_anchorTcpSocketStreamReader->LoadAsync(m_anchorStreamLength)).then([this](size_t bytesLoadedByStreamReader)
        {
            if (bytesLoadedByStreamReader > 0)
            {
                // Once the bytes are loaded, we can read them from the stream.
                m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0],
                    bytesLoadedByStreamReader));
                // Check status.
                if (bytesLoadedByStreamReader == m_anchorStreamLength)
                {
                    // The whole stream has arrived. We can process the data.
                    // Informational message of progress complete.
                    std::wstring infoMessage = std::to_wstring(bytesLoadedByStreamReader);
                    infoMessage += L" bytes read out of ";
                    infoMessage += std::to_wstring(m_anchorStreamLength);
                    infoMessage += L" total bytes; importing the data.\n";
                    OutputDebugStringW(infoMessage.c_str());
                    // Kick off a thread to wait for a new message indicating another incoming anchor data stream.
                    WaitForAnchorDataStream();
                    // Process the data for the stream we just received.
                    return SpatialAnchorImportExportHelper::ImportAnchorDataAsync(m_exportedAnchorStoreBytes, m_spatialAnchorHelper->GetAnchorMap());
                }
                else
                {
                    OutputDebugString(L"Error: Fewer than expected anchor data bytes were received.\n");
                }
            }
            else
            {
                OutputDebugString(L"Error: No anchor bytes were received.\n");
            }
            return task_from_result<bool>(false);
        });
    }
    else
    {
        OutputDebugString(L"Warning: A zero-length data buffer was sent.\n");
        return task_from_result<bool>(false);
    }
}

Mais uma vez, devemos estar preparados para lidar com erros de rede desconhecidos.

void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
    std::wstring error = L"Connection error: ";
    error += exception->ToString()->Data();
    error += L"\n";
    OutputDebugString(error.c_str());
}

Está feito! Agora, você deve ter informações suficientes para tentar localizar as âncoras recebidas pela rede. Novamente, observe que o cliente deve ter dados de rastreamento visual suficientes para que o espaço localize com sucesso a âncora; Se não funcionar imediatamente, tente andar por aí por um tempo. Se mesmo assim não funcionar, peça ao servidor que envie mais âncoras e use as comunicações de rede para concordar com uma que funcione para o cliente. Você pode experimentar isso baixando o HolographicSpatialAnchorTransferSample, configurando seus IPs de cliente e servidor e implantando-o em dispositivos HoloLens cliente e servidor.

Consulte também