Lokale ankeroverdrachten in DirectX

In situaties waarin u Azure Spatial Anchors niet kunt gebruiken, kunnen met lokale ankeroverdrachten één HoloLens apparaat een anker exporteren dat moet worden geïmporteerd door een tweede HoloLens apparaat.

Notitie

Lokale ankeroverdrachten bieden minder robuuste anker terugroepen dan Azure Spatial Anchors, en iOS- en Android-apparaten worden niet ondersteund door deze aanpak.

Notitie

De codefragmenten in dit artikel laten momenteel het gebruik van C++/CX zien in plaats van C++17-compatibele C++/WinRT zoals gebruikt in de C++ holografische projectsjabloon. De concepten zijn gelijkwaardig voor een C++/WinRT-project, maar u moet de code vertalen.

Ruimtelijke ankers overdragen

U kunt ruimtelijke ankers overdragen tussen Windows Mixed Reality apparaten met behulp van SpatialAnchorTransferManager. Met deze API kunt u een anker bundelen met alle ondersteunende sensorgegevens die nodig zijn om die exacte plaats ter wereld te vinden en die bundel vervolgens op een ander apparaat te importeren. Zodra de app op het tweede apparaat dat anker heeft geïmporteerd, kan elke app hologrammen weergeven met behulp van het coördinaatsysteem van het gedeelde ruimtelijke anker, dat vervolgens op dezelfde plaats in de echte wereld wordt weergegeven.

Houd er rekening mee dat ruimtelijke ankers niet kunnen worden overgedragen tussen verschillende apparaattypen, bijvoorbeeld een HoloLens ruimtelijk anker mogelijk niet kan worden gebruikt met een insluitende headset. Overgedragen ankers zijn ook niet compatibel met iOS- of Android-apparaten.

Uw app instellen voor het gebruik van de spatialPerception-mogelijkheid

Uw app moet zijn gemachtigd om de Mogelijkheid SpatialPerception te gebruiken voordat deze de SpatialAnchorTransferManager kan gebruiken. Dit is nodig omdat het overbrengen van een ruimtelijk anker het delen van sensorafbeeldingen omvat die in de loop van de tijd in de buurt van dat anker zijn verzameld, waaronder gevoelige informatie.

Declareer deze mogelijkheid in het bestand package.appxmanifest voor uw app. Hier volgt een voorbeeld:

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

De mogelijkheid is afkomstig van de uap2-naamruimte . Als u toegang wilt krijgen tot deze naamruimte in uw manifest, neemt u deze op als een xlmns-kenmerk in het <pakketelement> . Hier volgt een voorbeeld:

<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"
    >

OPMERKING: Uw app moet de mogelijkheid tijdens runtime aanvragen voordat deze toegang heeft tot SpatialAnchor-export-/import-API's. Zie RequestAccessAsync in de onderstaande voorbeelden.

Ankergegevens serialiseren door deze te exporteren met spatialAnchorTransferManager

Een helperfunctie is opgenomen in het codevoorbeeld voor het exporteren (serialiseren) van SpatialAnchor-gegevens . Deze export-API serialiseert alle ankers in een verzameling sleutel-waardeparen die tekenreeksen koppelen aan ankers.

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

Eerst moeten we de gegevensstroom instellen. Hierdoor kunnen we 1.) gebruik TryExportAnchorsAsync om de gegevens in een buffer te plaatsen die eigendom zijn van de app en 2.) gegevens lezen uit de geëxporteerde bytebufferstroom, een WinRT-gegevensstroom, naar onze eigen geheugenbuffer, een std::vectorbyte<>.

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

We moeten toestemming vragen voor toegang tot ruimtelijke gegevens, inclusief ankers die door het systeem worden geëxporteerd.

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

Als we wel machtigingen krijgen en ankers worden geëxporteerd, kunnen we de gegevensstroom lezen. Hier laten we ook zien hoe u de DataReader en InputStream maakt die we gebruiken om de gegevens te lezen.

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

Nadat we bytes uit de stream hebben gelezen, kunnen we ze zo opslaan in onze eigen gegevensbuffer.

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

Ankergegevens deserialiseren door deze in het systeem te importeren met behulp van SpatialAnchorTransferManager

Een helperfunctie is opgenomen in het codevoorbeeld om eerder geëxporteerde gegevens te laden. Deze deserialisatiefunctie biedt een verzameling sleutel-waardeparen, vergelijkbaar met wat de SpatialAnchorStore biedt, behalve dat we deze gegevens uit een andere bron hebben verkregen, zoals een netwerksocket. U kunt deze gegevens verwerken en redeneren voordat u deze offline opslaat, met behulp van in-app-geheugen of (indien van toepassing) de SpatialAnchorStore van uw 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
    )
{

Eerst moeten we streamobjecten maken om toegang te krijgen tot de ankergegevens. We schrijven de gegevens van onze buffer naar een systeembuffer, dus maken we een DataWriter die schrijft naar een gegevensstroom in het geheugen om ons doel te bereiken om ankers uit een bytebuffer in het systeem te krijgen als 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);

We moeten er opnieuw voor zorgen dat de app gemachtigd is om ruimtelijke ankergegevens te exporteren, waaronder persoonlijke informatie over de omgeving van de gebruiker.

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

Als toegang is toegestaan, kunnen we bytes van de buffer naar een systeemgegevensstroom schrijven.

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

Als het opslaan van bytes in de gegevensstroom lukt, kunnen we proberen die gegevens te importeren met behulp van 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);
    }

Als de gegevens kunnen worden geïmporteerd, krijgen we een kaartweergave van sleutel-waardeparen die tekenreeksen koppelen aan ankers. We kunnen dit laden in onze eigen in-memory gegevensverzameling en die verzameling gebruiken om te zoeken naar ankers die we willen gebruiken.

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

OPMERKING: Omdat u een anker kunt importeren, betekent dit niet noodzakelijkerwijs dat u het meteen kunt gebruiken. Het anker kan zich in een andere ruimte of een andere fysieke locatie bevinden; het anker kan pas worden verwijderd als het apparaat dat het heeft ontvangen, voldoende visuele informatie heeft over de omgeving waarin het anker is gemaakt, om de positie van het anker te herstellen ten opzichte van de bekende huidige omgeving. De clientimplementatie moet proberen het anker te vinden ten opzichte van uw lokale coördinaatsysteem of referentieframe voordat u doorgaat om het te gebruiken voor live-inhoud. Probeer bijvoorbeeld regelmatig het anker te vinden ten opzichte van een huidig coördinaatsysteem totdat het anker begint te locatable zijn.

Speciale overwegingen

Met de TryExportAnchorsAsync-API kunnen meerdere SpatialAnchors worden geëxporteerd naar dezelfde ondoorzichtige binaire blob. Er is echter een subtiel verschil in welke gegevens de blob bevat, afhankelijk van of één SpatialAnchor of meerdere SpatialAnchors in één aanroep worden geëxporteerd.

Export van één SpatialAnchor

De blob bevat een weergave van de omgeving in de omgeving van SpatialAnchor, zodat de omgeving kan worden herkend op het apparaat dat het SpatialAnchor importeert. Nadat het importeren is voltooid, is het nieuwe SpatialAnchor beschikbaar voor het apparaat. Ervan uitgaande dat de gebruiker onlangs in de buurt van het anker is geweest, kan deze worden gerenderd en kunnen hologrammen worden weergegeven die aan het SpatialAnchor zijn gekoppeld. Deze hologrammen worden weergegeven op dezelfde fysieke locatie als op het oorspronkelijke apparaat dat het SpatialAnchor heeft geëxporteerd.

Export of a single SpatialAnchor

Exporteren van meerdere SpatialAnchors

Net als bij de export van één SpatialAnchor bevat de blob een weergave van de omgeving in de omgeving van alle opgegeven SpatialAnchors. Daarnaast bevat de blob informatie over de verbindingen tussen de opgenomen SpatialAnchors, als deze zich in dezelfde fysieke ruimte bevinden. Dit betekent dat als twee nabijgelegen SpatialAnchors worden geïmporteerd, een hologram dat aan het tweede SpatialAnchor is gekoppeld, zelfs als het apparaat alleen de omgeving rond het eerste SpatialAnchor herkent, omdat er voldoende gegevens zijn om de transformatie tussen de twee SpatialAnchors te berekenen in de blob. Als de twee SpatialAnchors afzonderlijk zijn geëxporteerd (twee afzonderlijke aanroepen naar TryExportSpatialAnchors), zijn er mogelijk onvoldoende gegevens opgenomen in de blob voor hologrammen die zijn gekoppeld aan het tweede SpatialAnchor om te locatable wanneer de eerste zich bevindt.

Multiple anchors exported using a single TryExportAnchorsAsync callMultiple anchors exported using a separate TryExportAnchorsAsync call for each anchor

Voorbeeld: Ankergegevens verzenden met behulp van een Windows::Networking::StreamSocket

Hier geven we een voorbeeld van het gebruik van geëxporteerde ankergegevens door deze via een TCP-netwerk te verzenden. Dit is van holographicSpatialAnchorTransferSample.

De WinRT StreamSocket-klasse maakt gebruik van de PPL-taakbibliotheek. In het geval van netwerkfouten wordt de fout geretourneerd naar de volgende taak in de keten met behulp van een uitzondering die opnieuw wordt gegenereerd. De uitzondering bevat een HRESULT die de foutstatus aangeeft.

Gebruik een Windows::Networking::StreamSocketListener met TCP om geëxporteerde ankergegevens te verzenden

Maak een serverexemplaren die luistert naar een verbinding.

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

Wanneer een verbinding wordt ontvangen, gebruikt u de clientsocketverbinding om ankergegevens te verzenden.

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

Nu kunnen we beginnen met het verzenden van een gegevensstroom die de geëxporteerde ankergegevens bevat.

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

Voordat we de stream zelf kunnen verzenden, moeten we eerst een headerpakket verzenden. Dit headerpakket moet een vaste lengte hebben en moet ook de lengte aangeven van de variabele matrix van bytes die de ankergegevensstroom is; In het geval van dit voorbeeld hebben we geen andere headergegevens die moeten worden verzonden, dus onze koptekst is 4 bytes lang en bevat een 32-bits geheel getal zonder teken.

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

Zodra de lengte van de stroom, in bytes, naar de client is verzonden, kunnen we doorgaan met het schrijven van de gegevensstroom zelf naar de socketstroom. Dit zorgt ervoor dat de bytes van het ankerarchief naar de client worden verzonden.

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

Zoals eerder in dit onderwerp is vermeld, moeten we voorbereid zijn op het afhandelen van uitzonderingen met netwerkfoutstatusberichten. Voor fouten die niet worden verwacht, kunnen we de uitzonderingsgegevens zo schrijven naar de foutopsporingsconsole. Dit geeft ons een aanwijzing over wat er is gebeurd als het codevoorbeeld de verbinding niet kan voltooien of als het verzenden van de ankergegevens niet kan worden voltooid.

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

Gebruik een Windows::Networking::StreamSocket met TCP om geëxporteerde ankergegevens te ontvangen

Eerst moeten we verbinding maken met de server. In dit codevoorbeeld ziet u hoe u een StreamSocket maakt en configureert en hoe u een DataReader maakt die u kunt gebruiken om netwerkgegevens te verkrijgen met behulp van de socketverbinding.

OPMERKING: Als u deze voorbeeldcode uitvoert, moet u ervoor zorgen dat u de server configureert en start voordat u de client start.

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

Zodra er een verbinding is, kunnen we wachten totdat de server gegevens verzendt. We doen dit door LoadAsync aan te roepen voor de gegevenslezer van de stream.

De eerste set bytes die we ontvangen, moet altijd het headerpakket zijn, dat de bytelengte van de ankergegevensstroom aangeeft, zoals beschreven in de vorige sectie.

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

Nadat we het headerpakket hebben ontvangen, weten we hoeveel bytes ankergegevens we moeten verwachten. We kunnen doorgaan met het lezen van die bytes uit de stream.

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

Hier volgt onze code voor het ontvangen van de ankergegevensstroom. Nogmaals, we laden eerst de bytes uit de stream; Het kan enige tijd duren voordat deze bewerking is voltooid wanneer de StreamSocket wacht om die hoeveelheid bytes van het netwerk te ontvangen.

Wanneer de laadbewerking is voltooid, kunnen we dat aantal bytes lezen. Als we het aantal bytes hebben ontvangen dat we verwachten voor de ankergegevensstroom, kunnen we de ankergegevens importeren. Zo niet, dan moet er een soort fout zijn opgetreden. Dit kan bijvoorbeeld gebeuren wanneer het serverexemplaren worden beëindigd voordat het verzenden van de gegevensstroom kan worden voltooid of het netwerk uitvalt voordat de hele gegevensstroom door de client kan worden ontvangen.

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

Ook hier moeten we voorbereid zijn op het afhandelen van onbekende netwerkfouten.

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

Dat is alles. U moet nu voldoende informatie hebben om te proberen de ankers te vinden die via het netwerk zijn ontvangen. Houd er nogmaals rekening mee dat de client voldoende visuele traceringsgegevens moet hebben om de ruimte te kunnen vinden; Als het niet meteen werkt, probeer dan even rond te lopen. Als het nog steeds niet werkt, moet u de server meer ankers laten verzenden en netwerkcommunicatie gebruiken om akkoord te gaan met een netwerkcommunicatie die geschikt is voor de client. U kunt dit proberen door holographicSpatialAnchorTransferSample te downloaden, uw client- en server-IP-adressen te configureren en deze te implementeren op client- en serverapparaten HoloLens apparaten.

Zie ook