Lokalne transfery zakotwiczenia w programie DirectX

W sytuacjach, w których nie można użyć usługi Azure Spatial Anchors, transfery kotwic lokalnych umożliwiają jedno urządzenie HoloLens eksportowanie kotwicy do zaimportowania przez drugie urządzenie HoloLens.

Uwaga

Lokalne transfery zakotwiczeń zapewniają mniej niezawodne wycofanie zakotwiczenia niż usługa Azure Spatial Anchors, a urządzenia z systemami iOS i Android nie są obsługiwane w tym podejściu.

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX zamiast C++17 zgodnego z językiem C++/WinRT jako używanego w szablonie projektu holograficznego języka C++. Pojęcia te są równoważne projektowi C++/WinRT, ale trzeba będzie przetłumaczyć kod.

Przenoszenie zakotwiczeń przestrzennych

Kotwice przestrzenne można przenosić między urządzeniami Windows Mixed Reality przy użyciu narzędzia SpatialAnchorTransferManager. Ten interfejs API umożliwia utworzenie kotwicy ze wszystkimi pomocniczymi danymi czujnika potrzebnymi do znalezienia tego dokładnego miejsca na świecie, a następnie zaimportowania tego pakietu na innym urządzeniu. Gdy aplikacja na drugim urządzeniu zaimportowała tę kotwicę, każda aplikacja może renderować hologramy przy użyciu udostępnionego systemu współrzędnych kotwicy przestrzennej, który następnie będzie wyświetlany w tym samym miejscu w świecie rzeczywistym.

Należy pamiętać, że zakotwiczenia przestrzenne nie mogą być przenoszone między różnymi typami urządzeń, na przykład HoloLens zakotwiczenie przestrzenne może nie być lokalizowalne przy użyciu immersyjnego zestawu słuchawkowego. Przeniesione kotwice nie są również zgodne z urządzeniami z systemem iOS lub Android.

Konfigurowanie aplikacji w celu korzystania z funkcji spatialPerception

Aby móc używać funkcji SpatialPerception, aplikacja musi mieć uprawnienia do korzystania z funkcji SpatialAnchorTransferManager. Jest to konieczne, ponieważ transfer kotwicy przestrzennej obejmuje udostępnianie obrazów czujników zebranych w czasie w pobliżu tej kotwicy, która może obejmować poufne informacje.

Zadeklaruj tę funkcję w pliku package.appxmanifest dla aplikacji. Oto przykład:

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

Ta funkcja pochodzi z przestrzeni nazw uap2 . Aby uzyskać dostęp do tej przestrzeni nazw w manifeście, dołącz go jako atrybut xlmns w elemecie <Package> . Oto przykład:

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

UWAGA: Aplikacja będzie musiała zażądać możliwości w czasie wykonywania, zanim będzie mogła uzyskać dostęp do interfejsów API eksportu/importowania usługi SpatialAnchor. Zobacz RequestAccessAsync w poniższych przykładach.

Serializuj dane kotwicy, eksportując je za pomocą elementu SpatialAnchorTransferManager

Funkcja pomocnika jest uwzględniona w przykładzie kodu do eksportowania (serializowania) danych SpatialAnchor . Ten interfejs API eksportu serializuje wszystkie kotwice w kolekcji par klucz-wartość kojarząc ciągi z kotwicami.

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

Najpierw musimy skonfigurować strumień danych. Pozwoli to nam na 1.) Użyj polecenia TryExportAnchorsAsync, aby umieścić dane w buforze należącym do aplikacji i 2. odczyt danych ze strumienia wyeksportowanego buforu bajtów — który jest strumieniem danych WinRT — do naszego własnego buforu pamięci, który jest 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);

Musimy poprosić o uprawnienie dostępu do danych przestrzennych, w tym kotwic eksportowanych przez system.

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

Jeśli uzyskamy uprawnienia i kotwice zostaną wyeksportowane, możemy odczytać strumień danych. W tym miejscu pokazano również, jak utworzyć element DataReader i InputStream, za pomocą których będziemy odczytywać dane.

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

Po odczytaniu bajtów ze strumienia możemy zapisać je we własnym buforze danych.

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

Deserializowanie danych kotwicy przez zaimportowanie ich do systemu przy użyciu elementu SpatialAnchorTransferManager

Funkcja pomocnika jest uwzględniona w przykładzie kodu w celu załadowania wcześniej wyeksportowanych danych. Ta funkcja deserializacji udostępnia kolekcję par klucz-wartość, podobnie jak w przypadku funkcji SpatialAnchorStore , z wyjątkiem tego, że te dane pochodzą z innego źródła, takiego jak gniazdo sieciowe. Możesz przetworzyć i przyczynę tych danych przed zapisaniem ich w trybie offline, przy użyciu pamięci w aplikacji lub (jeśli ma to zastosowanie) w aplikacji 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
    )
{

Najpierw musimy utworzyć obiekty strumienia, aby uzyskać dostęp do danych kotwicy. Będziemy zapisywać dane z buforu systemu do buforu systemu, dlatego utworzymy maszynę DataWriter, która zapisuje w strumieniu danych w pamięci, aby osiągnąć nasz cel pobierania kotwic z buforu bajtowego do systemu jako usługi 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);

Po raz kolejny musimy upewnić się, że aplikacja ma uprawnienia do eksportowania danych zakotwiczenia przestrzennego, które mogą zawierać prywatne informacje o środowisku użytkownika.

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

Jeśli dostęp jest dozwolony, możemy zapisywać bajty z buforu do strumienia danych systemowych.

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

Jeśli udało nam się przechowywać bajty w strumieniu danych, możemy spróbować zaimportować te dane przy użyciu elementu 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);
    }

Jeśli dane można zaimportować, otrzymamy widok mapy par klucz-wartość kojarzący ciągi z kotwicami. Możemy załadować je do własnej kolekcji danych w pamięci i użyć tej kolekcji, aby wyszukać kotwice, których interesujemy.

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

UWAGA: Tylko dlatego, że można zaimportować kotwicę, niekoniecznie oznacza, że można go użyć od razu. Kotwica może znajdować się w innym pomieszczeniu lub w całości w innej lokalizacji fizycznej; kotwica nie będzie znajdować się do momentu, gdy urządzenie, które odebrało, ma wystarczającą ilość informacji wizualnych o środowisku, w których utworzono kotwicę, aby przywrócić położenie kotwicy względem znanego bieżącego środowiska. Implementacja klienta powinna spróbować zlokalizować kotwicę względem lokalnego systemu współrzędnych lub ramki odwołania przed kontynuowaniem próby użycia jej do zawartości na żywo. Na przykład spróbuj zlokalizować kotwicę względem bieżącego systemu współrzędnych okresowo, dopóki kotwica nie zacznie znajdować się w stanie lokalizowalnym.

Uwagi specjalne

Interfejs API TryExportAnchorsAsync umożliwia eksportowanie wielu obiektów SpatialAnchors do tego samego nieprzezroczystego binarnego obiektu blob. Istnieje jednak subtelna różnica między danymi, które będą uwzględniane przez obiekt blob, w zależności od tego, czy w jednym wywołaniu jest eksportowany pojedynczy obiekt SpatialAnchor czy wiele obiektów SpatialAnchor.

Eksportowanie pojedynczego elementu SpatialAnchor

Obiekt blob zawiera reprezentację środowiska w pobliżu obiektu SpatialAnchor, dzięki czemu środowisko można rozpoznać na urządzeniu, które importuje obiekt SpatialAnchor. Po zakończeniu importowania nowy program SpatialAnchor będzie dostępny dla urządzenia. Zakładając, że użytkownik był niedawno w pobliżu kotwicy, będzie można renderować lokalizacyjne i hologramy dołączone do obiektu SpatialAnchor. Te hologramy będą wyświetlane w tej samej lokalizacji fizycznej, którą wykonali na oryginalnym urządzeniu, które wyeksportowało element SpatialAnchor.

Export of a single SpatialAnchor

Eksportowanie wielu funkcji SpatialAnchors

Podobnie jak w przypadku eksportu pojedynczego obiektu SpatialAnchor obiekt blob zawiera reprezentację środowiska w pobliżu wszystkich określonych obiektów SpatialAnchor. Ponadto obiekt blob zawiera informacje o połączeniach między dołączonymi obiektami SpatialAnchors, jeśli znajdują się w tej samej przestrzeni fizycznej. Oznacza to, że w przypadku zaimportowania dwóch pobliskich obiektów SpatialAnchors hologram dołączony do drugiego obiektu SpatialAnchor będzie lokalizowany, nawet jeśli urządzenie rozpoznaje tylko środowisko wokół pierwszego obiektu SpatialAnchor, ponieważ wystarczająca ilość danych do obliczenia przekształcenia między dwoma obiektami SpatialAnchor została uwzględniona w obiekcie blob. Jeśli dwa elementy SpatialAnchors zostały wyeksportowane indywidualnie (dwa oddzielne wywołania do narzędzia TryExportSpatialAnchors), może nie być wystarczającej ilości danych zawartych w obiekcie blob dla hologramów dołączonych do drugiego obiektu SpatialAnchor do lokalizowania po zlokalizowaniu pierwszego.

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

Przykład: wysyłanie danych kotwicy przy użyciu Windows::Networking::StreamSocket

W tym miejscu przedstawiono przykład użycia wyeksportowanych danych kotwicy przez wysłanie ich przez sieć TCP. Pochodzi to z holographicSpatialAnchorTransferSample.

Klasa WinRT StreamSocket używa biblioteki zadań PPL. W przypadku błędów sieci błąd jest zwracany do następnego zadania w łańcuchu przy użyciu wyjątku, który jest ponownie zgłaszany. Wyjątek zawiera wartość HRESULT wskazującą stan błędu.

Użyj Windows::Networking::StreamSocketListener z protokołem TCP w celu wysyłania wyeksportowanych danych kotwicy

Utwórz wystąpienie serwera, które nasłuchuje połączenia.

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

Po odebraniu połączenia użyj połączenia gniazda klienta, aby wysłać dane kotwicy.

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

Teraz możemy rozpocząć wysyłanie strumienia danych zawierającego wyeksportowane dane kotwicy.

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

Zanim będziemy mogli wysłać sam strumień, musimy najpierw wysłać pakiet nagłówka. Ten pakiet nagłówka musi mieć stałą długość i musi również wskazywać długość zmiennej tablicy bajtów, które są strumieniem danych zakotwiczenia; w przypadku tego przykładu nie mamy żadnych innych danych nagłówka do wysłania, więc nasz nagłówek ma długość 4 bajtów i zawiera 32-bitową niepodpisaną liczbę całkowitą.

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

Gdy długość strumienia w bajtach zostanie wysłana do klienta, możemy kontynuować zapisywanie strumienia danych do strumienia gniazda. Spowoduje to wysłanie bajtów magazynu kotwicy do klienta.

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

Jak wspomniano wcześniej w tym temacie, musimy być przygotowani do obsługi wyjątków zawierających komunikaty o stanie błędu sieci. W przypadku błędów, które nie są oczekiwane, możemy zapisać informacje o wyjątku w konsoli debugowania w następujący sposób. Zapewni to nam wskazówkę co do tego, co się stało, jeśli nasz przykład kodu nie może ukończyć połączenia lub jeśli nie może zakończyć wysyłania danych kotwicy.

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

Używanie Windows::Networking::StreamSocket z protokołem TCP w celu odbierania wyeksportowanych danych kotwicy

Najpierw musimy nawiązać połączenie z serwerem. W tym przykładzie kodu pokazano, jak utworzyć i skonfigurować zestaw StreamSocket oraz utworzyć element DataReader, którego można użyć do uzyskiwania danych sieciowych przy użyciu połączenia gniazda.

UWAGA: Jeśli uruchomisz ten przykładowy kod, przed uruchomieniem klienta upewnij się, że serwer został skonfigurowany i uruchomiony.

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

Po nawiązaniu połączenia możemy poczekać na wysłanie danych przez serwer. W tym celu wywołamy funkcję LoadAsync w czytniku danych strumienia.

Pierwszy zestaw odbieranych bajtów powinien zawsze być pakietem nagłówka, który wskazuje długość bajtu strumienia danych kotwicy zgodnie z opisem w poprzedniej sekcji.

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

Po otrzymaniu pakietu nagłówka wiemy, ile bajtów danych kotwicy powinniśmy oczekiwać. Możemy kontynuować odczytywanie tych bajtów ze strumienia.

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

Oto nasz kod umożliwiający odbieranie strumienia danych zakotwiczenia. Ponownie załadujemy bajty ze strumienia; wykonanie tej operacji może zająć trochę czasu, ponieważ usługa StreamSocket czeka na odbieranie tej ilości bajtów z sieci.

Po zakończeniu operacji ładowania możemy odczytać tę liczbę bajtów. Jeśli otrzymaliśmy liczbę bajtów, których oczekujemy dla strumienia danych zakotwiczenia, możemy przejść do przodu i zaimportować dane kotwicy; jeśli nie, musi istnieć jakiś błąd. Na przykład może się to zdarzyć, gdy wystąpienie serwera zakończy działanie przed zakończeniem wysyłania strumienia danych lub sieć ulegnie awarii, zanim cały strumień danych zostanie odebrany przez klienta.

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

Ponownie musimy być przygotowani do obsługi nieznanych błędów sieci.

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

Gotowe. Teraz należy mieć wystarczającą ilość informacji, aby spróbować zlokalizować kotwice odebrane za pośrednictwem sieci. Ponownie należy pamiętać, że klient musi mieć wystarczającą ilość danych śledzenia wizualnego dla miejsca, aby pomyślnie zlokalizować kotwicę; jeśli nie działa od razu, spróbuj chodzić na chwilę. Jeśli nadal nie działa, serwer wysyła więcej kotwic i używa komunikacji sieciowej, aby uzgodnić ten, który działa dla klienta. Możesz to wypróbować, pobierając holographicSpatialAnchorTransferSample, konfigurując adresy IP klienta i serwera oraz wdrażając je na urządzeniach klienckich i serwerowych HoloLens.

Zobacz też