Trasferimenti di ancoraggi locali in DirectX

In situazioni in cui non è possibile usare ancoraggi nello spazio di Azure, i trasferimenti di ancoraggio locali consentono a un dispositivo HoloLens di esportare un ancoraggio da importare da un secondo dispositivo HoloLens.

Nota

I trasferimenti di ancoraggio locale forniscono un richiamo di ancoraggio meno affidabile rispetto agli ancoraggi nello spazio di Azure e i dispositivi iOS e Android non sono supportati da questo approccio.

Nota

I frammenti di codice in questo articolo illustrano attualmente l'uso di C++/CX anziché C++17 conforme a C++/WinRT come usato nel modello di progetto olografico C++. I concetti sono equivalenti per un progetto C++/WinRT, anche se sarà necessario tradurre il codice.

Trasferimento di ancoraggi spaziali

È possibile trasferire ancoraggi spaziali tra i dispositivi Windows Mixed Reality usando SpatialAnchorTransferManager. Questa API consente di raggruppare un ancoraggio con tutti i dati del sensore di supporto necessari per trovare la posizione esatta nel mondo e quindi importare tale bundle in un altro dispositivo. Dopo aver importato l'app nel secondo dispositivo, ogni app può eseguire il rendering degli ologrammi usando il sistema di coordinate dello spazio condiviso, che verrà quindi visualizzato nello stesso posto nel mondo reale.

Si noti che gli ancoraggi spaziali non sono in grado di trasferire tra diversi tipi di dispositivo, ad esempio un ancoraggio spaziale HoloLens potrebbe non essere locabile usando un visore visore immersivo. Gli ancoraggi trasferiti non sono compatibili anche con i dispositivi iOS o Android.

Configurare l'app per usare la funzionalità spatialPerception

L'app deve essere concessa l'autorizzazione per usare la funzionalità SpatialPerception prima di poter usare SpatialAnchorTransferManager. Ciò è necessario perché il trasferimento di un ancoraggio spaziale comporta la condivisione delle immagini del sensore raccolte nel tempo in prossimità di tale ancoraggio, che potrebbe includere informazioni riservate.

Dichiarare questa funzionalità nel file package.appxmanifest per l'app. Ecco un esempio:

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

La funzionalità proviene dallo spazio dei nomi uap2 . Per ottenere l'accesso a questo spazio dei nomi nel manifesto, includerlo come attributo xlmns nell'elemento <Package> . Ecco un esempio:

<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: L'app dovrà richiedere la funzionalità in fase di esecuzione prima di poter accedere alle API di esportazione/importazione di SpatialAnchor. Vedere RequestAccessAsync negli esempi seguenti.

Serializzare i dati di ancoraggio esportandolo con SpatialAnchorTransferManager

Una funzione helper è inclusa nell'esempio di codice per esportare (serializzare) dati SpatialAnchor . Questa API di esportazione serializza tutti gli ancoraggi in una raccolta di coppie chiave-valore che associano stringhe con ancoraggi.

// 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
    )
{

Prima di tutto, è necessario configurare il flusso di dati. Ciò ci consentirà di 1.) usare TryExportAnchorsAsync per inserire i dati in un buffer di proprietà dell'app e 2. leggere i dati dal flusso di buffer di byte esportato, ovvero un flusso di dati WinRT, nel buffer di memoria personalizzato, ovvero un byte> std::vector<.

// 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);

È necessario chiedere l'autorizzazione per accedere ai dati spaziali, inclusi gli ancoraggi esportati dal 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 si ottengono le autorizzazioni e gli ancoraggi vengono esportati, è possibile leggere il flusso di dati. In questo articolo viene anche illustrato come creare DataReader e InputStream per leggere i dati.

// 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);
    }

Dopo aver letto byte dal flusso, è possibile salvarli nel proprio buffer di dati, ad esempio.

}).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;
    }
});
};

Deserializzare i dati di ancoraggio importandoli nel sistema usando SpatialAnchorTransferManager

Una funzione helper è inclusa nell'esempio di codice per caricare i dati esportati in precedenza. Questa funzione di deserializzazione fornisce una raccolta di coppie chiave-valore, simili a quelle fornite dall'archivio SpatialAnchor, ad eccezione del fatto che sono stati ottenuti questi dati da un'altra origine, ad esempio un socket di rete. È possibile elaborare e ragionare su questi dati prima di archiviarlo offline, usando memoria in-app o (se applicabile) spazioAnchorStore dell'app.

// 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
    )
{

Prima di tutto, è necessario creare oggetti di flusso per accedere ai dati di ancoraggio. Si scriveranno i dati dal buffer a un buffer di sistema, quindi si creerà un DataWriter che scrive in un flusso di dati in memoria per raggiungere l'obiettivo di ottenere ancoraggi da un buffer di byte nel sistema come 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);

Ancora una volta, è necessario assicurarsi che l'app disponga dell'autorizzazione per esportare i dati di ancoraggio spaziale, che potrebbero includere informazioni private sull'ambiente dell'utente.

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

Se è consentito l'accesso, è possibile scrivere byte dal buffer a un flusso di dati di 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 si è riusciti ad archiviare byte nel flusso di dati, è possibile provare a importare tali dati usando 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 i dati sono in grado di essere importati, viene visualizzata una visualizzazione mappa delle coppie chiave-valore che associano stringhe con ancoraggi. È possibile caricarlo nella raccolta di dati in memoria e usare tale raccolta per cercare ancoraggi che sono interessati all'uso.

}).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: Solo perché è possibile importare un ancoraggio, non significa necessariamente che è possibile usarlo immediatamente. L'ancoraggio potrebbe trovarsi in una stanza diversa o in un'altra posizione fisica completamente; l'ancoraggio non sarà locabile fino a quando il dispositivo che ha ricevuto ha informazioni visive sufficienti sull'ambiente in cui è stato creato l'ancoraggio, per ripristinare la posizione dell'ancoraggio rispetto all'ambiente corrente noto. L'implementazione client deve provare a individuare l'ancoraggio relativo al sistema di coordinate locale o al frame di riferimento prima di continuare a usarlo per il contenuto live. Ad esempio, provare a individuare l'ancoraggio rispetto a un sistema di coordinate corrente periodicamente fino a quando l'ancoraggio inizia a essere locabile.

Considerazioni speciali

L'API TryExportAnchorsAsync consente l'esportazione di più spazianchor nello stesso BLOB binario opaco. Tuttavia, esiste una differenza sottile nei dati che il BLOB includerà, a seconda che un singolo oggetto SpatialAnchor o più SpatialAnchor venga esportato in una singola chiamata.

Esportazione di un singolo oggetto SpatialAnchor

Il BLOB contiene una rappresentazione dell'ambiente nelle vicinanze di SpatialAnchor in modo che l'ambiente possa essere riconosciuto nel dispositivo che importa spatialAnchor. Al termine dell'importazione, il nuovo oggetto SpatialAnchor sarà disponibile per il dispositivo. Supponendo che l'utente sia stato recentemente in prossimità dell'ancoraggio, sarà locatable e gli ologrammi collegati a SpatialAnchor possono essere sottoposti a rendering. Questi ologrammi verranno visualizzati nella stessa posizione fisica che hanno fatto sul dispositivo originale che ha esportato spatialAnchor.

Esportazione di un singolo oggetto SpatialAnchor

Esportazione di più spazianchor

Analogamente all'esportazione di un singolo oggetto SpatialAnchor, il BLOB contiene una rappresentazione dell'ambiente nelle vicinanze di tutti gli elementi SpatialAnchors specificati. Inoltre, il BLOB contiene informazioni sulle connessioni tra gli spazi spaziali inclusi, se si trovano nello stesso spazio fisico. Ciò significa che se vengono importati due spazi spaziali vicini, un ologramma collegato al secondo SpatialAnchor sarebbe locatable anche se il dispositivo riconosce solo l'ambiente intorno al primo spatialAnchor, perché i dati sufficienti per calcolare la trasformazione tra i due spatialAnchor sono stati inclusi nel BLOB. Se i due spazianchor sono stati esportati singolarmente (due chiamate separate a TryExportSpatialAnchors) potrebbero non esserci dati sufficienti inclusi nel BLOB per gli ologrammi collegati al secondo oggetto SpatialAnchor che deve essere locabile quando si trova il primo.

Più ancoraggi esportati usando una singola chiamata TryExportAnchorsAsync esportata tramite una chiamataTryExportAnchorsAsync separata per ogni ancoraggio

Esempio: Inviare dati di ancoraggio tramite windows::Networking::StreamSocket

In questo caso viene fornito un esempio di come usare i dati di ancoraggio esportati inviandolo in una rete TCP. Si tratta di HolographicSpatialAnchorTransferSample.

La classe WinRT StreamSocket usa la libreria di attività PPL. Nel caso di errori di rete, l'errore viene restituito all'attività successiva nella catena usando un'eccezione generata di nuovo. L'eccezione contiene un valore HRESULT che indica lo stato dell'errore.

Usare un oggetto Windows::Networking::StreamSocketListener con TCP per inviare dati di ancoraggio esportati

Creare un'istanza del server in ascolto di una connessione.

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 viene ricevuta una connessione, usare la connessione socket client per inviare dati di ancoraggio.

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());
    }
}

A questo punto, è possibile iniziare a inviare un flusso di dati contenente i dati di ancoraggio esportati.

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");
        }
    });
}

Prima di poter inviare il flusso stesso, è prima necessario inviare un pacchetto di intestazione. Questo pacchetto di intestazione deve essere di lunghezza fissa e deve anche indicare la lunghezza della matrice di byte variabile che è il flusso di dati di ancoraggio; nel caso di questo esempio non sono presenti altri dati di intestazione da inviare, quindi l'intestazione è lunga 4 byte e contiene un intero senza segno a 32 bit.

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);

Una volta inviata la lunghezza del flusso, in byte, al client, è possibile procedere alla scrittura del flusso di dati stesso nel flusso socket. Ciò causerà l'invio dei byte dell'archivio di ancoraggio al client.

}).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);
    });
}

Come indicato in precedenza in questo argomento, è necessario essere preparati per gestire le eccezioni contenenti messaggi di stato degli errori di rete. Per gli errori che non sono previsti, è possibile scrivere le informazioni sulle eccezioni nella console di debug, ad esempio. In questo modo verrà dato un indizio su ciò che è successo se l'esempio di codice non è in grado di completare la connessione o se non è in grado di completare l'invio dei dati di ancoraggio.

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

Usare un oggetto Windows::Networking::StreamSocket con TCP per ricevere dati di ancoraggio esportati

Prima di tutto, è necessario connettersi al server. Questo esempio di codice illustra come creare e configurare un streamSocket e creare un DataReader che è possibile usare per acquisire dati di rete usando la connessione socket.

NOTA: Se si esegue questo codice di esempio, assicurarsi di configurare e avviare il server prima di avviare il client.

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);
    }
}

Dopo avere una connessione, è possibile attendere che il server invii dati. Questa operazione viene eseguita chiamando LoadAsync nel lettore di dati di flusso.

Il primo set di byte ricevuti deve essere sempre il pacchetto di intestazione, che indica la lunghezza del byte dei dati di ancoraggio, come descritto nella sezione precedente.

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);
}

Dopo aver ricevuto il pacchetto di intestazione, è necessario sapere quanti byte di dati di ancoraggio dovrebbero essere previsti. È possibile procedere alla lettura di tali byte dal flusso.

}).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);
        }
    });
}

Ecco il codice per ricevere il flusso di dati di ancoraggio. Di nuovo, si caricheranno prima i byte dal flusso; questa operazione potrebbe richiedere tempo per completare perché StreamSocket attende di ricevere tale quantità di byte dalla rete.

Al termine dell'operazione di caricamento, è possibile leggere il numero di byte. Se è stato ricevuto il numero di byte previsti per il flusso di dati di ancoraggio, è possibile procedere e importare i dati di ancoraggio; in caso contrario, è necessario che ci sia stato un certo tipo di errore. Ad esempio, ciò può verificarsi quando l'istanza del server termina prima di poter completare l'invio del flusso di dati oppure la rete viene interrotta prima che l'intero flusso di dati possa essere ricevuto dal client.

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);
    }
}

Di nuovo, è necessario essere pronti per gestire errori di rete sconosciuti.

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

L'operazione è terminata. A questo momento, dovrebbero essere disponibili informazioni sufficienti per provare a individuare gli ancoraggi ricevuti in rete. Anche in questo caso, si noti che il client deve disporre di dati di rilevamento visivi sufficienti per individuare correttamente lo spazio; se non funziona subito, prova a camminare per un po'. Se non funziona ancora, fare in modo che il server invii più ancoraggi e usare le comunicazioni di rete per concordare un'operazione valida per il client. È possibile provare questo problema scaricando HolographicSpatialAnchorTransferSample, configurando gli INDIRIZZI IP client e server e distribuendoli nei dispositivi HoloLens client e server.

Vedi anche