Udostępnij za pośrednictwem


Lokalne transfery kotwic w programie DirectX

Transfery zakotwiczeń lokalnych umożliwiają jednemu urządzeniu HoloLens eksportowanie kotwicy do zaimportowania przez drugie urządzenie HoloLens.

Uwaga

Takie podejście nie jest obsługiwane na urządzeniach z systemami iOS i Android.

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX, a nie C++17 zgodnego ze standardem C++/WinRT używanego w szablonie projektu holograficznego języka C++. Koncepcje są równoważne projektowi C++/WinRT, chociaż trzeba będzie przetłumaczyć kod.

Transferowanie kotwic przestrzennych

Za pomocą narzędzia SpatialAnchorTransferManager można przenosić kotwice przestrzenne między urządzeniami Windows Mixed Reality. 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 tego współużytkowanego systemu współrzędnych kotwicy przestrzennej, który będzie następnie wyświetlany w tym samym miejscu w świecie rzeczywistym.

Należy pamiętać, że kotwice przestrzenne nie mogą być przenoszone między różnymi typami urządzeń, na przykład kotwica przestrzenna HoloLens może nie być lokalizowalny 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

Aplikacja musi mieć uprawnienie do korzystania z funkcji SpatialPerception, zanim będzie mogła korzystać z elementu SpatialAnchorTransferManager. Jest to konieczne, ponieważ przeniesienie kotwicy przestrzennej obejmuje udostępnianie obrazów czujników zebranych w czasie w pobliżu tej kotwicy, która może zawierać poufne informacje.

Zadeklaruj tę możliwość 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.

Serializowanie danych kotwicy przez wyeksportowanie ich za pomocą elementu SpatialAnchorTransferManager

Funkcja pomocnika jest zawarta w przykładzie kodu do eksportowania (serializowania) danych SpatialAnchor . Ten interfejs API eksportu serializuje wszystkie kotwice w kolekcji par klucz-wartość kojarzące 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 narzędzia TryExportAnchorsAsync, aby umieścić dane w buforze należącym do aplikacji i 2. odczytywanie danych ze strumienia buforu wyeksportowanego bajtu bajtów — czyli strumienia danych WinRT — do naszego własnego buforu pamięci, który jest bajtem std::vector<>.

// Create a random access stream to process the anchor byte data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor byte stream.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);

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 zawarta w przykładzie kodu w celu załadowania wcześniej wyeksportowanych danych. Ta funkcja deserializacji udostępnia kolekcję par klucz-wartość, podobnie jak zapewnia funkcja SpatialAnchorStore — z wyjątkiem tego, że te dane pochodzą z innego źródła, takiego jak gniazdo sieciowe. Możesz przetworzyć i uzasadnić te dane przed zapisaniem ich w trybie offline, przy użyciu pamięci w aplikacji lub (jeśli ma to zastosowanie) 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, więc utworzymy element DataWriter, który zapisuje w strumieniu danych w pamięci, aby osiągnąć nasz cel uzyskania kotwic z buforu bajtowego do systemu jako 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 chcemy użyć.

}).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żywać od razu. Kotwica może znajdować się w innym pomieszczeniu lub w całości w innej lokalizacji fizycznej; kotwica nie będzie można zlokalizować, dopóki urządzenie, które je 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 referencyjnej przed kontynuowaniem próby użycia jej do zawartości na żywo. Na przykład spróbuj zlokalizować kotwicę względem bieżącego układu współrzędnych okresowo, aż kotwica zacznie znajdować się w stanie lokalizowalnym.

Kwestie szczególne

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 pojedynczy obiekt SpatialAnchor czy wiele obiektów SpatialAnchor są eksportowane w jednym wywołaniu.

Eksportowanie pojedynczego obiektu 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 element 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ć lokalizatory 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.

Eksportowanie pojedynczego obiektu SpatialAnchor

Eksportowanie wielu obiektów SpatialAnchors

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

Wiele kotwic eksportowanych przy użyciu pojedynczego wywołania TryExportAnchorsAsync Wiele kotwic eksportowanych przy użyciu oddzielnego wywołania TryExportAnchorsAsync dla każdej kotwicy

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

W tym miejscu przedstawiono przykład użycia wyeksportowanych danych kotwicy przez wysłanie ich przez sieć TCP. To jest 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 zostanie ponownie zgłoszony. Wyjątek zawiera wartość HRESULT wskazującą stan błędu.

Używanie elementu Windows::Networking::StreamSocketListener z protokołem TCP do 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");
        }
    });
}

Przed wysłaniem samego strumienia musimy najpierw wysłać pakiet nagłówkowy. Ten pakiet nagłówka musi mieć stałą długość i musi również wskazywać długość zmiennej tablicy bajtów, która jest 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);

Po wysłaniu strumienia w bajtach do klienta możemy kontynuować zapisywanie samego strumienia danych w strumieniu 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łędów 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ładowy kod 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 systemu Windows::Networking::StreamSocket z protokołem TCP do 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łujemy funkcję LoadAsync w czytniku danych strumienia.

Pierwszy zestaw odbieranych bajtów powinien być zawsze 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 do odbierania strumienia danych kotwicy. Ponownie załadujemy najpierw bajty ze strumienia; wykonanie tej operacji może zająć trochę czasu, ponieważ funkcja StreamSocket czeka na odebranie 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());
}

I już! 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 to nie zadziała od razu, spróbuj chodzić przez jakiś czas. Jeśli nadal nie działa, serwer wysyła więcej kotwic i używa komunikacji sieciowej, aby uzgodnić, 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ż