Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las transferencias de delimitadores locales permiten que un dispositivo HoloLens exporte un delimitador para que lo importe un segundo dispositivo HoloLens.
Nota:
Este enfoque no admite dispositivos iOS y Android.
Nota:
Los fragmentos de código de este artículo muestran actualmente el uso de C++/CX en lugar de C++17 compatible con C++/WinRT como se usa en la plantilla de proyecto holográfico de C++. Los conceptos son equivalentes para un proyecto de C++/WinRT, aunque deberá traducir el código.
Transferencia de delimitadores espaciales
Puede transferir delimitadores espaciales entre Windows Mixed Reality dispositivos mediante SpatialAnchorTransferManager. Esta API le permite agrupar un delimitador con todos los datos de sensor auxiliares necesarios para encontrar ese lugar exacto en el mundo y, a continuación, importar ese paquete en otro dispositivo. Una vez que la aplicación del segundo dispositivo ha importado ese delimitador, cada aplicación puede representar hologramas mediante el sistema de coordenadas de ese delimitador espacial compartido, que aparecerá en el mismo lugar del mundo real.
Tenga en cuenta que los delimitadores espaciales no pueden transferirse entre diferentes tipos de dispositivos, por ejemplo, es posible que un delimitador espacial de HoloLens no se pueda localizar mediante un casco envolvente. Los delimitadores transferidos tampoco son compatibles con dispositivos iOS o Android.
Configuración de la aplicación para usar la funcionalidad spatialPerception
Se debe conceder a la aplicación permiso para usar la funcionalidad SpatialPerception para poder usar SpatialAnchorTransferManager. Esto es necesario porque la transferencia de un delimitador espacial implica compartir imágenes de sensor recopiladas a lo largo del tiempo en las proximidades de ese delimitador, lo que podría incluir información confidencial.
Declare esta funcionalidad en el archivo package.appxmanifest de la aplicación. Aquí le mostramos un ejemplo:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
La funcionalidad procede del espacio de nombres uap2 . Para obtener acceso a este espacio de nombres en el manifiesto, insclúyelo como un atributo xlmns en el <elemento Package> . Aquí le mostramos un ejemplo:
<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"
>
NOTA: La aplicación tendrá que solicitar la funcionalidad en tiempo de ejecución para poder acceder a las API de exportación e importación de SpatialAnchor. Vea RequestAccessAsync en los ejemplos siguientes.
Serializar datos de delimitador exportándolo con SpatialAnchorTransferManager
Una función auxiliar se incluye en el ejemplo de código para exportar (serializar) datos de SpatialAnchor . Esta API de exportación serializa todos los delimitadores de una colección de pares clave-valor que asocian cadenas con delimitadores.
// 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
)
{
En primer lugar, es necesario configurar el flujo de datos. Esto nos permitirá 1. use TryExportAnchorsAsync para colocar los datos en un búfer propiedad de la aplicación y 2. leer datos del flujo de búfer de bytes exportado ,que es un flujo de datos de WinRT, en nuestro propio búfer de memoria, que es un byte> 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);
Es necesario solicitar permiso para acceder a datos espaciales, incluidos los delimitadores exportados por el sistema.
// 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);
}
});
Si obtenemos permiso y se exportan delimitadores, podemos leer el flujo de datos. Aquí también se muestra cómo crear dataReader y InputStream que usaremos para leer los datos.
// 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);
}
Después de leer bytes de la secuencia, podemos guardarlos en nuestro propio búfer de datos.
}).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;
}
});
};
Deserializar datos delimitadores importándolo en el sistema mediante SpatialAnchorTransferManager
Se incluye una función auxiliar en el ejemplo de código para cargar los datos exportados anteriormente. Esta función de deserialización proporciona una colección de pares clave-valor, de forma similar a lo que proporciona SpatialAnchorStore, salvo que obtuvimos estos datos de otro origen, como un socket de red. Puedes procesar y razonar sobre estos datos antes de almacenarlos sin conexión, usar la memoria en la aplicación o (si procede) spatialAnchorStore de la aplicación.
// 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
)
{
En primer lugar, es necesario crear objetos de secuencia para acceder a los datos delimitadores. Escribiremos los datos de nuestro búfer en un búfer del sistema, por lo que crearemos un objeto DataWriter que escriba en un flujo de datos en memoria para lograr nuestro objetivo de obtener delimitadores de un búfer de bytes en el sistema como 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);
Una vez más, debemos asegurarnos de que la aplicación tenga permiso para exportar datos de anclaje espacial, lo que podría incluir información privada sobre el entorno del usuario.
// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
[&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Access is allowed.
Si se permite el acceso, podemos escribir bytes desde el búfer en un flujo de datos del sistema.
// 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);
}
Si hemos almacenado correctamente bytes en el flujo de datos, podemos intentar importar esos datos mediante 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);
}
Si los datos se pueden importar, se obtiene una vista de mapa de pares clave-valor que asocian cadenas con delimitadores. Podemos cargarlo en nuestra propia recopilación de datos en memoria y usar esa colección para buscar delimitadores que nos interesen usar.
}).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;
});
}
NOTA: El solo hecho de que pueda importar un delimitador no significa necesariamente que pueda usarlo de inmediato. El delimitador podría estar en otra sala u otra ubicación física por completo; el delimitador no se podrá localizar hasta que el dispositivo que lo recibió tenga suficiente información visual sobre el entorno en el que se creó el delimitador, para restaurar la posición del delimitador en relación con el entorno actual conocido. La implementación del cliente debe intentar localizar el delimitador en relación con el sistema de coordenadas local o el marco de referencia antes de continuar para intentar usarlo para contenido dinámico. Por ejemplo, intente localizar periódicamente el delimitador en relación con un sistema de coordenadas actual hasta que el delimitador empiece a localizarse.
Consideraciones especiales
La API TryExportAnchorsAsync permite exportar varios spatialAnchors al mismo blob binario opaco. Sin embargo, hay una diferencia sutil en qué datos incluirá el blob, en función de si un único spatialAnchor o varios spatialAnchors se exportan en una sola llamada.
Exportación de un único elemento SpatialAnchor
El blob contiene una representación del entorno en las proximidades de SpatialAnchor para que el entorno se pueda reconocer en el dispositivo que importa SpatialAnchor. Una vez completada la importación, el nuevo SpatialAnchor estará disponible para el dispositivo. Suponiendo que el usuario haya estado recientemente cerca del delimitador, será localizable y se podrán representar hologramas conectados a SpatialAnchor. Estos hologramas se mostrarán en la misma ubicación física que hicieron en el dispositivo original que exportó SpatialAnchor.
Exportación de varios spatialAnchors
Al igual que la exportación de un único spatialAnchor, el blob contiene una representación del entorno en las proximidades de todos los SpatialAnchors especificados. Además, el blob contiene información sobre las conexiones entre los SpatialAnchors incluidos, si se encuentran en el mismo espacio físico. Esto significa que si se importan dos SpatialAnchors cercanos, se podría localizar un holograma conectado al segundo SpatialAnchor aunque el dispositivo solo reconozca el entorno alrededor del primer SpatialAnchor, porque se incluyeron suficientes datos para calcular la transformación entre los dos SpatialAnchors en el blob. Si los dos SpatialAnchors se exportaron individualmente (dos llamadas independientes a TryExportSpatialAnchors), es posible que no haya suficientes datos incluidos en el blob para que los hologramas adjuntos al segundo SpatialAnchor se puedan localizar cuando se encuentre el primero.
Ejemplo: Envío de datos delimitadores mediante Windows::Networking::StreamSocket
Aquí se proporciona un ejemplo de cómo usar los datos de anclaje exportados enviándolo a través de una red TCP. Se trata de HolographicSpatialAnchorTransferSample.
La clase StreamSocket de WinRT usa la biblioteca de tareas PPL. En el caso de errores de red, el error se devuelve a la siguiente tarea de la cadena mediante una excepción que se vuelve a producir. La excepción contiene un valor HRESULT que indica el estado del error.
Uso de Windows::Networking::StreamSocketListener con TCP para enviar datos de anclaje exportados
Cree una instancia de servidor que escuche una conexión.
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");
}
}
Cuando se recibe una conexión, use la conexión de socket de cliente para enviar datos delimitadores.
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());
}
}
Ahora, podemos empezar a enviar un flujo de datos que contiene los datos de delimitador exportados.
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");
}
});
}
Antes de poder enviar la secuencia en sí, primero debemos enviar un paquete de encabezado. Este paquete de encabezado debe tener una longitud fija y también debe indicar la longitud de la matriz variable de bytes que es el flujo de datos delimitador; en el caso de este ejemplo no tenemos ningún otro dato de encabezado que enviar, por lo que nuestro encabezado tiene 4 bytes de longitud y contiene un entero de 32 bits sin signo.
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);
Una vez que la longitud del flujo, en bytes, se ha enviado al cliente, podemos continuar con la escritura del flujo de datos en el flujo de socket. Esto hará que los bytes del almacén delimitador se envíen al cliente.
}).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);
});
}
Como se indicó anteriormente en este tema, debemos estar preparados para controlar las excepciones que contienen mensajes de estado de error de red. Para los errores que no se esperan, podemos escribir la información de excepción en la consola de depuración de este modo. Esto nos dará una pista sobre lo que ocurrió si nuestro ejemplo de código no puede completar la conexión o si no puede terminar de enviar los datos del delimitador.
void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Connection error: ") +
exception->ToString()->Data() +
L"\n"
);
}
Uso de Windows::Networking::StreamSocket con TCP para recibir datos de delimitador exportados
En primer lugar, tenemos que conectarnos al servidor. En este ejemplo de código se muestra cómo crear y configurar un StreamSocket y cómo crear un Objeto DataReader que puede usar para adquirir datos de red mediante la conexión de socket.
NOTA: Si ejecuta este código de ejemplo, asegúrese de configurar e iniciar el servidor antes de iniciar el cliente.
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);
}
}
Una vez que tenemos una conexión, podemos esperar a que el servidor envíe datos. Para ello, llamamos a LoadAsync en el lector de datos de flujo.
El primer conjunto de bytes que recibimos siempre debe ser el paquete de encabezado, que indica la longitud de bytes del flujo de datos delimitador como se describe en la sección anterior.
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);
}
Después de recibir el paquete de encabezado, sabemos cuántos bytes de datos de delimitador debemos esperar. Podemos continuar leyendo esos bytes de la secuencia.
}).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);
}
});
}
Este es nuestro código para recibir el flujo de datos delimitador. De nuevo, primero cargaremos los bytes de la secuencia; Esta operación puede tardar algún tiempo en completarse, ya que StreamSocket espera a recibir esa cantidad de bytes de la red.
Una vez completada la operación de carga, podemos leer ese número de bytes. Si recibimos el número de bytes que esperamos para el flujo de datos de delimitador, podemos continuar e importar los datos del delimitador; si no es así, debe haber habido algún tipo de error. Por ejemplo, esto puede ocurrir cuando la instancia del servidor finaliza antes de que pueda terminar de enviar el flujo de datos, o la red deja de funcionar antes de que el cliente pueda recibir toda la secuencia de datos.
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);
}
}
De nuevo, debemos estar preparados para controlar errores de red desconocidos.
void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
std::wstring error = L"Connection error: ";
error += exception->ToString()->Data();
error += L"\n";
OutputDebugString(error.c_str());
}
Y eso es todo. Ahora, debe tener suficiente información para intentar localizar los delimitadores recibidos a través de la red. Una vez más, tenga en cuenta que el cliente debe tener suficientes datos de seguimiento visual para que el espacio busque correctamente el delimitador; si no funciona de inmediato, pruebe a caminar por un tiempo. Si todavía no funciona, haga que el servidor envíe más delimitadores y use las comunicaciones de red para acordar una que funcione para el cliente. Para probar esto, descargue HolographicSpatialAnchorTransferSample, configure las direcciones IP de cliente y servidor e impleméntelas en dispositivos HoloLens de cliente y servidor.