Lokala fästpunktsöverföringar i DirectX

I situationer där du inte kan använda Azure Spatial Anchorskan lokala fästpunktsöverföringar göra så att en HoloLens-enhet exporterar en fästpunkt som ska importeras av en HoloLens enhet.

Anteckning

Lokala fästpunktsöverföringar ger mindre robust återkallning av fästpunkter än Azure Spatial Anchors,och iOS- och Android-enheter stöds inte av den här metoden.

Anteckning

Kodfragmenten i den här artikeln visar för närvarande användning av C++/CX i stället för C++17-kompatibel C++/WinRT som används i C++ holographic-projektmallen. Begreppen motsvarar ett C++/WinRT-projekt, men du måste översätta koden.

Överföra rumsliga fästpunkter

Du kan överföra spatiala fästpunkter mellan Windows Mixed Reality med hjälp av SpatialAnchorTransferManager. Med det här API:et kan du samla in en fästpunkt med alla sensordata som behövs för att hitta den exakta platsen i världen och sedan importera paketet på en annan enhet. När appen på den andra enheten har importerat den fästpunkten kan varje app rendera hologram med hjälp av det delade spatiala fästpunktens koordinatsystem, som sedan visas på samma plats i verkligheten.

Observera att spatiala fästpunkter inte kan överföras mellan olika enhetstyper, till exempel att en HoloLens spatial fästpunkt kanske inte kan användas med ett integrerande headset. Överförda fästpunkter är inte heller kompatibla med iOS- eller Android-enheter.

Konfigurera din app att använda spatialPerception-funktionen

Din app måste beviljas behörighet att använda SpatialPerception-funktionen innan den kan använda SpatialAnchorTransferManager. Detta är nödvändigt eftersom överföring av en spatial fästpunkt inbegriper delning av sensorbilder som samlats in över tid i närheten av fästpunkten, vilket kan innehålla känslig information.

Deklarera den här funktionen i filen package.appxmanifest för din app. Här är ett exempel:

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

Funktionen kommer från uap2-namnområdet. Om du vill få åtkomst till det här namnområdet i manifestet inkluderar du det som ett xlmns-attribut < i Package>-elementet. Här är ett exempel:

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

Obs! Din app måste begära funktionen vid körning innan den kan komma åt SPATIALAnchor-API:er för export/import. Se RequestAccessAsync i exemplen nedan.

Serialisera fästpunktsdata genom att exportera dem med SpatialAnchorTransferManager

En hjälpfunktion ingår i kodexemppelet för att exportera (serialisera) SpatialAnchor-data. Det här export-API:et serialiserar alla fästpunkter i en samling nyckel/värde-par som associerar strängar med fästpunkter.

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

Först måste vi konfigurera dataströmmen. Detta gör att vi kan 1.) använd TryExportAnchorsAsync för att placera data i en buffert som ägs av appen och 2.) läsa data från den exporterade bytebuffertströmmen – som är en WinRT-dataström – till vår egen minnesbuffert, som är en 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);

Vi måste be om behörighet att komma åt rumsliga data, inklusive fästpunkter som exporteras av systemet.

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

Om vi får behörighet och fästpunkter exporteras kan vi läsa dataströmmen. Här visar vi också hur du skapar DataReader och InputStream som vi ska använda för att läsa data.

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

När vi har läst byte från dataströmmen kan vi spara dem i vår egen databuffert så här.

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

Deserialisera fästpunktsdata genom att importera dem till systemet med hjälp av SpatialAnchorTransferManager

En hjälpfunktion ingår i kodexe exempel för att läsa in tidigare exporterade data. Den här deserialiseringsfunktionen tillhandahåller en samling nyckel/värde-par som liknar vad SpatialAnchorStore tillhandahåller, förutom att vi har fått dessa data från en annan källa, till exempel en nätverkssocket. Du kan bearbeta och orsaken till dessa data innan du lagrar dem offline, med hjälp av appens minne eller (om tillämpligt) appens SpatialAnchorStore.

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

Först måste vi skapa strömobjekt för att komma åt fästpunktsdata. Vi kommer att skriva data från vår buffert till en systembuffert, så vi skapar en DataWriter som skriver till en minnes minnesbuffrad dataström för att uppnå vårt mål med att hämta fästpunkter från en bytebuffert till systemet som 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);

Vi måste återigen se till att appen har behörighet att exportera spatiala fästpunktsdata, vilket kan innehålla privat information om användarens miljö.

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

Om åtkomst tillåts kan vi skriva byte från bufferten till en systemdataström.

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

Om vi lyckades lagra byte i dataströmmen kan vi försöka importera dessa data med hjälp av 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);
    }

Om data kan importeras får vi en kartvy över nyckel/värde-par som associerar strängar med fästpunkter. Vi kan läsa in detta i vår egen minnesinsamling och använda den samlingen för att leta efter fästpunkter som vi är intresserade av att använda.

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

Obs! Bara för att du kan importera en fästpunkt innebär det inte nödvändigtvis att du kan använda den direkt. Fästpunkten kan finnas i ett annat rum eller en annan fysisk plats helt och hållet. Fästpunkten är inte tillgänglig förrän enheten som tog emot den har tillräckligt med visuell information om miljön som fästpunkten skapades i för att återställa fästpunktens position i förhållande till den kända aktuella miljön. Klientimplementering bör försöka hitta fästpunkten i förhållande till ditt lokala koordinatsystem eller referensram innan du fortsätter med att försöka använda den för live-innehåll. Prova till exempel att hitta fästpunkten i förhållande till ett aktuellt koordinatsystem regelbundet tills fästpunkten börjar vara oskadlig.

Särskilda överväganden

Med API:et TryExportAnchorsAsync kan flera SpatialAnchors exporteras till samma täckande binära blob. Det finns dock en subtil skillnad i vilka data som bloben kommer att innehålla, beroende på om en enda SpatialAnchor eller flera SpatialAnchors exporteras i ett enda anrop.

Export av en enda SpatialAnchor

Bloben innehåller en representation av miljön i närheten av SpatialAnchor så att miljön kan identifieras på den enhet som importerar SpatialAnchor. När importen är klar blir den nya SpatialAnchor tillgänglig för enheten. Förutsatt att användaren nyligen har varit i närheten av fästpunkten är den oanvändbar och hologram som är kopplade till SpatialAnchor kan återges. Dessa hologram visas på samma fysiska plats som de gjorde på den ursprungliga enheten som exporterade SpatialAnchor.

Export av en enda SpatialAnchor

Export av flera SpatialAnchors

Precis som exporten av en enda SpatialAnchor innehåller bloben en representation av miljön i närheten av alla angivna SpatialAnchors. Dessutom innehåller bloben information om anslutningarna mellan de inkluderade SpatialAnchors, om de finns i samma fysiska utrymme. Det innebär att om två närliggande SpatialAnchors importeras så skulle ett hologram som är kopplat till den andra SpatialAnchor vara oskadligt även om enheten bara känner igen miljön runt den första SpatialAnchor, eftersom tillräckligt med data för att beräkna transformeringen mellan de två SpatialAnchors inkluderades i bloben. Om de två SpatialAnchors exporterades individuellt (två separata anrop till TryExportSpatialAnchors) kanske det inte finns tillräckligt med data i bloben för att hologram som är kopplade till den andra SpatialAnchor ska vara oskadliga när den första finns.

Flera fästpunkter som exporteras med ett enda TryExportAnchorsAsync-anrop Flera fästpunkter som exporteras med ett separat TryExportAnchorsAsync-anrop för varje fästpunkt

Exempel: Skicka fästpunktsdata med en Windows::Nätverk::StreamSocket

Här ger vi ett exempel på hur du använder exporterade fästpunktsdata genom att skicka dem via ett TCP-nätverk. Det här är från HolographicSpatialAnchorTransferSample.

Klassen WinRT StreamSocket använder PPL-uppgiftsbiblioteket. När det gäller nätverksfel returneras felet till nästa uppgift i kedjan med ett undantag som returneras igen. Undantaget innehåller ett HRESULT som anger felstatusen.

Använd en Windows::Networking::StreamSocketListener med TCP för att skicka exporterade fästpunktsdata

Skapa en serverinstans som lyssnar efter en anslutning.

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

När en anslutning tas emot använder du klientsocketanslutningen för att skicka fästpunktsdata.

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 kan vi börja skicka en dataström som innehåller exporterade fästpunktsdata.

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

Innan vi kan skicka själva dataströmmen måste vi först skicka ett huvudpaket. Det här huvudpaketet måste ha en fast längd och måste också ange längden på den variabelmatris med byte som är fästpunktsdataströmmen. I det här exemplet har vi inga andra huvuddata att skicka, så vår rubrik är 4 byte lång och innehåller ett 32-bitars heltal utan tecken.

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

När strömlängden i byte har skickats till klienten kan vi fortsätta att skriva själva dataströmmen till socketströmmen. Detta gör att ankarlagrets byte skickas till klienten.

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

Som tidigare nämnts i det här avsnittet måste vi vara förberedda på att hantera undantag som innehåller meddelanden om nätverksfelstatus. För fel som inte förväntas kan vi skriva undantagsinformationen till felsökningskonsolen så här. Detta ger oss en ledtråd om vad som hände om vårt kodexempel inte kan slutföra anslutningen, eller om det inte går att slutföra sändningen av fästpunktsdata.

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

Använd en Windows::Networking::StreamSocket med TCP för att ta emot exporterade fästpunktsdata

Först måste vi ansluta till servern. Det här kodexe exemplet visar hur du skapar och konfigurerar en StreamSocket och skapar en DataReader som du kan använda för att hämta nätverksdata med socketanslutningen.

Obs! Om du kör den här exempelkoden måste du konfigurera och starta servern innan du startar klienten.

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

När vi har en anslutning kan vi vänta tills servern skickar data. Det gör vi genom att anropa LoadAsync på dataströmsläsaren.

Den första uppsättningen byte som vi tar emot bör alltid vara huvudpaketet, vilket anger bytelängden för ankardataströmmen enligt beskrivningen i föregående avsnitt.

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

När vi har tagit emot huvudpaketet vet vi hur många byte fästpunktsdata vi bör förvänta oss. Vi kan fortsätta att läsa dessa byte från dataströmmen.

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

Här är vår kod för att ta emot ankardataströmmen. Återigen läser vi först in byte från dataströmmen. Den här åtgärden kan ta lite tid att slutföra eftersom StreamSocket väntar på att ta emot den mängden byte från nätverket.

När inläsningen är klar kan vi läsa det antalet byte. Om vi har fått det antal byte som vi förväntar oss för ankardataströmmen kan vi importera fästpunktsdata. Annars måste det ha uppstått någon typ av fel. Detta kan till exempel inträffa när serverinstansen avslutas innan den kan slutföra sändningen av dataströmmen, eller om nätverket slutar att gälla innan hela dataströmmen kan tas emot av klienten.

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

Vi måste återigen vara förberedda på att hantera okända nätverksfel.

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

Klart! Nu bör du ha tillräckligt med information för att försöka hitta de fästpunkter som tas emot över nätverket. Observera återigen att klienten måste ha tillräckligt med visuella spårningsdata för att utrymmet ska kunna hitta fästpunkten. Om det inte fungerar direkt kan du prova att gå runt ett tag. Om det fortfarande inte fungerar kan servern skicka fler fästpunkter och använda nätverkskommunikation för att komma överens om en som fungerar för klienten. Du kan prova detta genom att ladda ned HolographicSpatialAnchorTransferSample, konfigurera klientens och serverns IP-adresser och distribuera den till klient- och serverenheter HoloLens enheter.

Se även