Lokale ankeroverdrachten in DirectX

In situaties waarin u Azure Spatial Anchorsniet kunt gebruiken, kunnen lokale ankeroverdrachten één HoloLens-apparaat inschakelen om een anker te exporteren dat door een tweede HoloLens geïmporteerd.

Notitie

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

Notitie

De codefragmenten in dit artikel demonstreren momenteel het gebruik van C++/CX in plaats van C++17-compatibele C++/WinRT, zoals gebruikt in de C++-holographic-projectsjabloon. De concepten zijn gelijk aan 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 de 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 importeren op een ander apparaat. Zodra de app op het tweede apparaat dat anker heeft geïmporteerd, kan elke app hologrammen renderen met behulp van het coördinatensysteem van dat gedeelde ruimtelijke anker, dat vervolgens op dezelfde plaats in de echte wereld wordt weergegeven.

Houd er rekening mee dat ruimtelijke ankers niet kunnen worden overdraagbaar tussen verschillende typen apparaten, bijvoorbeeld een HoloLens ruimtelijk anker kan mogelijk niet worden lokaliseren met behulp van een immersive headset. Overgedragen ankers zijn ook niet compatibel met iOS- of Android-apparaten.

Uw app instellen voor het gebruik van de mogelijkheid spatialPerception

Aan uw app moet toestemming worden verleend voor het gebruik van de spatialPerception-functie voordat deze de SpatialAnchorTransferManager kan gebruiken. Dit is nodig omdat bij het overbrengen van een ruimtelijk anker sensorafbeeldingen moeten worden gedeeld die gedurende een periode in de omgeving van dat anker zijn verzameld, die mogelijk gevoelige informatie bevatten.

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, moet u deze opnemen 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 kan krijgen tot SpatialAnchor-API's voor exporteren/importeren. Zie RequestAccessAsync in de onderstaande voorbeelden.

Serialiseer ankergegevens door deze te exporteren met spatialAnchorTransferManager

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

// 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. Hiermee kunnen we 1.) gebruik TryExportAnchorsAsync om de gegevens in een buffer te zetten die eigendom is van de app en 2.) gegevens lezen uit de geëxporteerde bytebufferstroom ( een WinRT-gegevensstroom) in onze eigen geheugenbuffer, een std::vector < byte>.

// 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 stroom hebben gelezen, kunnen we ze op deze manier 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;
    }
});
};

Deserialiseer ankergegevens door deze in het systeem te importeren met behulp van spatialAnchorTransferManager

Er is een helperfunctie opgenomen in het codevoorbeeld om eerder geëxporteerde gegevens te laden. Deze deserialiseringsfunctie biedt een verzameling sleutel-waardeparen, vergelijkbaar met wat SpatialAnchorStore biedt, behalve dat we deze gegevens van een andere bron hebben, zoals een netwerksocker. U kunt deze gegevens verwerken en beredeneren voordat u ze 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 stroomobjecten maken voor toegang tot de ankergegevens. We schrijven de gegevens van onze buffer naar een systeembuffer, dus maken we een DataWriter die naar een gegevensstroom in het geheugen schrijft om ons doel te bereiken om ankers van een bytebuffer als SpatialAnchors in het systeem te krijgen.

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

Nogmaals, we moeten ervoor zorgen dat de app toestemming heeft om ruimtelijke ankergegevens te exporteren, die persoonlijke informatie over de omgeving van de gebruiker kunnen bevatten.

// 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 schrijven van de buffer naar een systeemgegevensstroom.

// 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 is gelukt om bytes op te slaan in de gegevensstroom, 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 aan ankers koppelen. 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: Het feit dat u een anker kunt importeren, betekent niet noodzakelijkerwijs dat u het meteen kunt gebruiken. Het anker kan zich in een andere ruimte of volledig op een andere fysieke locatie bevinden; het anker kan pas worden gevonden 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 ten opzichte van de bekende huidige omgeving te herstellen. De client-implementatie moet proberen het anker ten opzichte van uw lokale coördinaatsysteem of referentieframe te vinden voordat u doorgaat met het gebruik ervan voor live-inhoud. Probeer bijvoorbeeld periodiek het anker te lokaliseren ten opzichte van een huidig coördinaatsysteem totdat het anker kan worden gevonden.

Speciale overwegingen

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

Exporteren 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 spatialAnchor importeert. Nadat het importeren is voltooid, is de nieuwe SpatialAnchor beschikbaar voor het apparaat. Ervan uitgaande dat de gebruiker onlangs in de buurt van het anker is geweest, is het te lokaliseren en kunnen hologrammen die zijn gekoppeld aan spatialAnchor worden weergegeven. Deze hologrammen worden op dezelfde fysieke locatie als op het oorspronkelijke apparaat dat spatialAnchor heeft geëxporteerd, weer te zien.

Exporteren van één SpatialAnchor

Exporteren van meerdere SpatialAnchors

Net als bij het exporteren van één SpatialAnchor bevat de blob een weergave van de omgeving in de buurt 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 er twee nabijgelegen SpatialAnchors worden geïmporteerd, een hologram dat is gekoppeld aan de tweede SpatialAnchor, lokeerbaar is, zelfs als het apparaat alleen de omgeving rond de eerste SpatialAnchor herkent, omdat er voldoende gegevens zijn opgenomen om 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 niet voldoende gegevens opgenomen in de blob voor hologrammen die zijn gekoppeld aan de tweede SpatialAnchor om te kunnen worden gevonden wanneer de eerste is gevonden.

Meerdere ankers geëxporteerd met één TryExportAnchorsAsync-aanroep Meerdere ankers geëxporteerd met behulp van een afzonderlijke TryExportAnchorsAsync-aanroep voor elk anker

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

Hier geven we een voorbeeld van hoe u geëxporteerde ankergegevens kunt gebruiken door ze 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 opgetreden. De uitzondering bevat een HRESULT die de foutstatus aangeeft.

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

Maak een server-exemplaar dat 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 clientsockeverbinding 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 stroom 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 om te verzenden, dus onze header 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. Hierdoor worden de bytes van het ankeropslag verzonden naar de 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);
    });
}

Zoals eerder in dit onderwerp is vermeld, moeten we voorbereid zijn op het afhandelen van uitzonderingen die netwerkfoutstatusberichten bevatten. Voor fouten die niet worden verwacht, kunnen we de uitzonderingsgegevens als volgt naar de foutopsporingsconsole schrijven. Dit geeft ons een aanwijzing over wat er is gebeurd als ons 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 gebruikt, 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 tot de server gegevens verzendt. We doen dit door LoadAsync aan te roepen in de gegevenslezer van de stream.

De eerste set bytes die we ontvangen, moet altijd het headerpakket zijn. Dit geeft de lengte aan van de byte van de ankergegevensstroom, 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 aan ankergegevens we kunnen verwachten. We kunnen doorgaan met het lezen van deze 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);
        }
    });
}

Dit is onze code voor het ontvangen van de ankergegevensstroom. Nogmaals, we laden eerst de bytes uit de stroom; Het kan enige tijd duren voordat deze bewerking is voltooid, omdat de StreamSocket wacht op het ontvangen van die hoeveelheid bytes van het netwerk.

Wanneer de laadbewerking is voltooid, kunnen we dat aantal bytes lezen. Als we het aantal bytes ontvangen dat we verwachten voor de ankergegevensstroom, kunnen we de ankergegevens gaan importeren; Zo niet, dan moet er een fout zijn opgetreden. Dit kan bijvoorbeeld gebeuren wanneer het serverin exemplaar wordt beëindigd voordat het verzenden van de gegevensstroom kan worden beëindigd, of als het netwerk uit bedrijf gaat voordat de volledige 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 ook rekening mee dat de client voldoende visuele traceringsgegevens moet hebben voor de ruimte om het anker te kunnen vinden; Als het niet meteen werkt, probeer dan even rond te lopen. Als het nog steeds niet werkt, laat u de server meer ankers verzenden en netwerkcommunicatie gebruiken om een verbinding te maken die voor de client werkt. U kunt dit uitproberen door HolographicSpatialAnchorTransferSample te downloaden, uw client- en server-IP's te configureren en deze te implementeren op client- en server-HoloLens apparaten.

Zie ook