Локальная передача привязки в DirectX
Локальная передача привязки позволяет одному устройству HoloLens экспортировать привязку, импортируемую вторым устройством HoloLens.
Примечание.
Устройства iOS и Android не поддерживаются этим подходом.
Примечание.
Фрагменты кода в этой статье в настоящее время демонстрируют использование C++/CX, а не C++17-совместимых C++/WinRT, как используется в шаблоне голографического проекта C++/WinRT. Основные понятия эквивалентны для проекта C++/WinRT, хотя вам потребуется перевести код.
Передача пространственных привязок
Пространственные привязки можно передавать между устройствами Windows Смешанная реальность с помощью SpatialAnchorTransferManager. Этот API позволяет упаковать привязку со всеми вспомогательными данными датчика, необходимыми для поиска точного места в мире, а затем импортировать этот пакет на другое устройство. После импорта приложения на втором устройстве каждое приложение может отображать голограммы с помощью системы координат общей пространственной привязки, которая будет отображаться в том же месте в реальном мире.
Обратите внимание, что пространственные привязки не могут передаваться между различными типами устройств, например пространственные привязки HoloLens могут быть недоступны с помощью иммерсивной гарнитуры. Передаваемые привязки также несовместимы с устройствами iOS или Android.
Настройка приложения для использования функции spatialPerception
Приложение должно быть предоставлено разрешение на использование возможности SpatialPerception, прежде чем он сможет использовать SpatialAnchorTransferManager. Это необходимо, так как передача пространственной привязки включает общий доступ к изображениям датчиков, собранным с течением времени в окрестностях этой привязки, которая может включать конфиденциальную информацию.
Объявите эту возможность в файле package.appxmanifest для приложения. Приведем пример:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
Эта возможность поступает из пространства имен uap2 . Чтобы получить доступ к этому пространству имен в манифесте, добавьте его в качестве атрибута xlmns в <элемент Package> . Приведем пример:
<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"
>
ПРИМЕЧАНИЕ. Приложению потребуется запросить возможность во время выполнения, прежде чем получить доступ к API экспорта и импорта SpatialAnchor. См. инструкции RequestAccessAsync в приведенных ниже примерах.
Сериализация данных привязки путем экспорта с помощью SpatialAnchorTransferManager
В пример кода включена функция вспомогательной функции для экспорта (сериализации) данных SpatialAnchor . Этот API экспорта сериализует все привязки в коллекции пар "ключ-значение", связывающих строки с привязками.
// 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
)
{
Сначала необходимо настроить поток данных. Это позволит нам 1.) используйте TryExportAnchorsAsync, чтобы поместить данные в буфер, принадлежащий приложению, и 2.) считывает данные из экспортированного потока буфера байтов , который является потоком данных WinRT, в собственный буфер памяти, который представляет собой байт> 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);
Нам нужно попросить разрешение на доступ к пространственным данным, включая привязки, экспортированные системой.
// 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);
}
});
Если мы получаем разрешение и привязки экспортируются, можно считывать поток данных. Здесь также показано, как создать DataReader и InputStream, которые будут использоваться для чтения данных.
// 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);
}
После того как мы считываем байты из потока, мы можем сохранить их в собственном буфере данных, как это так.
}).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;
}
});
};
Десериализация данных привязки путем импорта в систему с помощью SpatialAnchorTransferManager
Вспомогательные функции включены в пример кода для загрузки ранее экспортированных данных. Эта функция десериализации предоставляет коллекцию пар "ключ-значение", аналогичную тому, что предоставляет SpatialAnchorStore, за исключением того, что мы получили эти данные из другого источника, например сетевого сокета. Вы можете обрабатывать и причины этих данных, прежде чем хранить их в автономном режиме, используя память в приложении или (если применимо) для приложения 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
)
{
Сначала необходимо создать объекты потоковой передачи для доступа к данным привязки. Мы будем записывать данные из буфера в системный буфер, поэтому мы создадим DataWriter, который записывает в поток данных в памяти, чтобы достичь нашей цели получения привязок из буфера байтов в систему как 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);
Еще раз необходимо убедиться, что приложение имеет разрешение на экспорт данных пространственной привязки, которые могут включать в себя частную информацию о среде пользователя.
// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
[&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Access is allowed.
Если доступ разрешен, мы можем записывать байты из буфера в системный поток данных.
// 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);
}
Если мы успешно храним байты в потоке данных, мы можем попытаться импортировать эти данные с помощью 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);
}
Если данные могут быть импортированы, мы получаем представление карты пар "ключ-значение", связывающих строки с привязками. Мы можем загрузить это в собственную коллекцию данных в памяти и использовать эту коллекцию для поиска привязок, которые мы заинтересованы в использовании.
}).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;
});
}
ПРИМЕЧАНИЕ. Только потому, что вы можете импортировать привязку, не обязательно означает, что ее можно использовать сразу. Привязка может находиться в другой комнате или в другом физическом расположении полностью; Привязка не будет размещаться, пока устройство, получивщее его, имеет достаточно визуальных сведений об среде, в которой была создана привязка, чтобы восстановить положение привязки относительно известной текущей среды. Реализация клиента должна попытаться найти привязку относительно локальной системы координат или эталонного кадра, прежде чем продолжить использовать ее для динамического содержимого. Например, попробуйте периодически найти привязку относительно текущей системы координат до тех пор, пока привязка не начнет быть неуловимой.
Особые соображения
API TryExportAnchorsAsync позволяет экспортировать несколько пространственныхanchors в один непрозрачный двоичный большой двоичный объект. Однако есть тонкое различие в том, какие данные будут включать большой двоичный объект, в зависимости от того, экспортируются ли один объект SpatialAnchor или несколько ПространственныхAnchors в одном вызове.
Экспорт одного объекта SpatialAnchor
Большой двоичный объект содержит представление среды в окрестностях Объекта SpatialAnchor, чтобы среда была распознана на устройстве, импортируемом ПространственнымAnchor. После завершения импорта новый ПространственныйAnchor будет доступен устройству. Предполагая, что пользователь недавно был в непосредственной близости от привязки, он будет неустанным и голограммы, подключенные к SpatialAnchor, могут быть отрисованы. Эти голограммы будут отображаться в том же физическом расположении, что и на исходном устройстве, экспортируемом SpatialAnchor.
Экспорт нескольких пространственныхanchors
Как и экспорт одного объекта SpatialAnchor, большой двоичный объект содержит представление среды в окрестностях всех указанных ПространственныхAnchors. Кроме того, большой двоичный объект содержит сведения о соединениях между включенными пространственнымиAnchors, если они находятся в одном физическом пространстве. Это означает, что если два близлежащих пространственныхAnchors импортируются, то голограмма, подключенная ко второму ПространственномуAnchor, будет неустанно ставиться, даже если устройство распознает среду вокруг первого ПространственногоAnchor, так как достаточно данных для вычисления преобразования между двумя пространственнымиAnchor были включены в большой двоичный объект. Если два пространственныхanchors были экспортированы по отдельности (два отдельных вызова TryExportSpatialAnchors), то может быть недостаточно данных, включенных в большой двоичный объект для голограмм, подключенных ко второму ПространственномуAnchor, чтобы быть неуклюжимым при расположении первого.
Пример. Отправка данных привязки с помощью Windows::Networking::StreamSocket
Ниже приведен пример использования экспортированных данных привязки, отправляя их по сети TCP. Это из HolographicSpatialAnchorTransferSample.
Класс WinRT StreamSocket использует библиотеку задач PPL. В случае ошибок сети ошибка возвращается в следующую задачу в цепочке, используя исключение, которое создается повторно. Исключение содержит HRESULT, указывающее состояние ошибки.
Использование Windows::Networking::StreamSocketListener с TCP для отправки экспортированных данных привязки
Создайте экземпляр сервера, прослушивающий подключение.
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");
}
}
При получении подключения используйте подключение сокета клиента для отправки данных привязки.
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());
}
}
Теперь можно начать отправлять поток данных, содержащий экспортированные данные привязки.
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");
}
});
}
Прежде чем отправлять сам поток, сначала необходимо отправить пакет заголовка. Этот пакет заголовка должен иметь фиксированную длину, и она также должна указывать длину массива переменных байтов, который является потоком данных привязки; В этом примере у нас нет других данных заголовка для отправки, поэтому наш заголовок имеет длину 4 байта и содержит 32-разрядное целое число без знака.
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);
После отправки клиенту длины потока в байтах можно продолжить запись потока данных в поток сокета. Это приведет к тому, что байты хранилища привязки отправляются клиенту.
}).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);
});
}
Как отмечалось ранее в этом разделе, необходимо подготовиться к обработке исключений, содержащих сообщения о состоянии сетевых ошибок. Для ошибок, которые не ожидались, можно написать сведения об исключении в консоль отладки, как показано ниже. Это даст нам подсказку о том, что произошло, если наш пример кода не может завершить подключение, или если не удается завершить отправку данных привязки.
void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Connection error: ") +
exception->ToString()->Data() +
L"\n"
);
}
Использование Windows::Networking::StreamSocket с TCP для получения экспортированных данных привязки
Сначала необходимо подключиться к серверу. В этом примере кода показано, как создать и настроить StreamSocket и создать DataReader, который можно использовать для получения сетевых данных с помощью подключения сокета.
ПРИМЕЧАНИЕ. Если вы запускаете этот пример кода, убедитесь, что вы настроите и запустите сервер перед запуском клиента.
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);
}
}
Когда у нас есть подключение, мы можем ждать отправки данных сервером. Для этого вызовите LoadAsync в средстве чтения данных потока.
Первый набор байтов, которые мы получаем, всегда должен быть пакет заголовка, который указывает длину потока данных привязки, как описано в предыдущем разделе.
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);
}
Получив пакет заголовка, мы знаем, сколько байтов данных привязки следует ожидать. Мы можем продолжить чтение этих байтов из потока.
}).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);
}
});
}
Вот наш код для получения потока данных привязки. Снова мы сначала загрузим байты из потока; Эта операция может занять некоторое время, так как StreamSocket ожидает получения этого количества байтов из сети.
После завершения операции загрузки можно считывать это число байтов. Если мы получили количество байтов, которые мы ожидаем для потока данных привязки, мы можем идти вперед и импортировать данные привязки; Если нет, должна быть какая-то ошибка. Например, это может произойти, когда экземпляр сервера завершится до завершения отправки потока данных, или сеть исчезнет до того, как весь поток данных может быть получен клиентом.
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);
}
}
Опять же, мы должны быть готовы к обработке неизвестных сетевых ошибок.
void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
std::wstring error = L"Connection error: ";
error += exception->ToString()->Data();
error += L"\n";
OutputDebugString(error.c_str());
}
Вот и все! Теперь у вас должно быть достаточно сведений, чтобы попытаться найти привязки, полученные по сети. Опять же, обратите внимание, что у клиента должно быть достаточно данных визуального отслеживания для успешного поиска привязки; если он не работает сразу, попробуйте пройтись в течение некоторого времени. Если он по-прежнему не работает, отправьте сервер больше якорей и используйте сетевые коммуникации, чтобы согласиться с тем, который работает для клиента. Это можно попробовать, скачав HolographicSpatialAnchorTransferSample, настроив IP-адреса клиента и сервера, а также развернув его на клиентских и серверных устройствах HoloLens.