Transferts d’ancre locale dans DirectX
Dans les situations où vous ne pouvez pas utiliser Azure Spatial Anchors, les transferts d’ancre locaux permettent à un appareil HoloLens d’exporter une ancre à importer par un deuxième appareil HoloLens.
Remarque
Les transferts d’ancres locales fournissent un rappel d’ancre moins robuste que Les ancres spatiales Azure, et les appareils iOS et Android ne sont pas pris en charge par cette approche.
Remarque
Les extraits de code de cet article illustrent actuellement l’utilisation de C++/CX plutôt que C++17 conforme à C++/WinRT comme utilisé dans le modèle de projet holographique C++. Les concepts sont équivalents pour un projet C++/WinRT, bien que vous deviez traduire le code.
Transfert d’ancres spatiales
Vous pouvez transférer des ancres spatiales entre des appareils Windows Mixed Reality à l’aide de SpatialAnchorTransferManager. Cette API vous permet de regrouper une ancre avec toutes les données de capteur de prise en charge nécessaires pour trouver cet emplacement exact dans le monde, puis d’importer ce bundle sur un autre appareil. Une fois que l’application sur le deuxième appareil a importé cette ancre, chaque application peut restituer des hologrammes à l’aide du système de coordonnées de l’ancre spatiale partagée, qui apparaîtra ensuite dans le même endroit dans le monde réel.
Notez que les ancres spatiales ne peuvent pas être transférées entre différents types d’appareils, par exemple une ancre spatiale HoloLens ne peut pas être locatable à l’aide d’un casque immersif. Les ancres transférées ne sont pas compatibles avec les appareils iOS ou Android.
Configurer votre application pour utiliser la fonctionnalité spatialPerception
Votre application doit être autorisée à utiliser la fonctionnalité SpatialPerception pour pouvoir utiliser SpatialAnchorTransferManager. Cela est nécessaire, car le transfert d’une ancre spatiale implique le partage d’images de capteur collectées au fil du temps à proximité de cette ancre, ce qui peut inclure des informations sensibles.
Déclarez cette fonctionnalité dans le fichier package.appxmanifest pour votre application. Voici un exemple :
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
La fonctionnalité provient de l’espace de noms uap2 . Pour accéder à cet espace de noms dans votre manifeste, incluez-le en tant qu’attribut xlmns dans l’élément <Package> . Voici un exemple :
<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"
>
REMARQUE : Votre application doit demander la fonctionnalité au moment de l’exécution avant de pouvoir accéder aux API d’exportation/importation spatialAnchor. Consultez RequestAccessAsync dans les exemples ci-dessous.
Sérialiser les données d’ancre en l’exportant avec SpatialAnchorTransferManager
Une fonction d’assistance est incluse dans l’exemple de code pour exporter (sérialiser) les données SpatialAnchor . Cette API d’exportation sérialise toutes les ancres dans une collection de paires clé-valeur associant des chaînes à des ancres.
// 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
)
{
Tout d’abord, nous devons configurer le flux de données. Cela nous permettra de passer à 1.) utilisez TryExportAnchorsAsync pour placer les données dans une mémoire tampon appartenant à l’application et 2.) lit les données du flux de mémoire tampon d’octet exporté , qui est un flux de données WinRT , dans notre propre mémoire tampon, qui est un octet> 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);
Nous devons demander l’autorisation d’accéder aux données spatiales, y compris les ancres exportées par le système.
// 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 nous obtenons des autorisations et des ancres sont exportées, nous pouvons lire le flux de données. Ici, nous montrons également comment créer DataReader et InputStream que nous utiliserons pour lire les données.
// 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);
}
Une fois que nous avons lu des octets à partir du flux, nous pouvons les enregistrer dans notre propre mémoire tampon de données comme suit.
}).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;
}
});
};
Désérialiser les données d’ancre en les important dans le système à l’aide de SpatialAnchorTransferManager
Une fonction d’assistance est incluse dans l’exemple de code pour charger des données précédemment exportées. Cette fonction de désérialisation fournit une collection de paires clé-valeur, similaires à ce que fournit SpatialAnchorStore, sauf que nous avons obtenu ces données à partir d’une autre source, comme un socket réseau. Vous pouvez traiter et raisonner ces données avant de les stocker hors connexion, à l’aide de la mémoire dans l’application ou (le cas échéant) du SpatialAnchorStore de votre application.
// 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
)
{
Tout d’abord, nous devons créer des objets de flux pour accéder aux données d’ancrage. Nous allons écrire les données de notre mémoire tampon vers une mémoire tampon système. Nous allons donc créer un DataWriter qui écrit dans un flux de données en mémoire afin d’atteindre notre objectif d’obtenir des ancres à partir d’une mémoire tampon d’octets dans le système en tant que 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);
Une fois de plus, nous devons nous assurer que l’application dispose de l’autorisation d’exporter des données d’ancre spatiale, ce qui peut inclure des informations privées sur l’environnement de l’utilisateur.
// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
[&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Access is allowed.
Si l’accès est autorisé, nous pouvons écrire des octets de la mémoire tampon dans un flux de données système.
// 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 nous avons réussi à stocker des octets dans le flux de données, nous pouvons essayer d’importer ces données à l’aide de 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 les données peuvent être importées, nous obtenons une vue cartographique des paires clé-valeur associant des chaînes à des ancres. Nous pouvons le charger dans notre propre collection de données en mémoire et utiliser cette collection pour rechercher des ancres qui nous intéressent.
}).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;
});
}
REMARQUE : Juste parce que vous pouvez importer une ancre, cela ne signifie pas nécessairement que vous pouvez l’utiliser immédiatement. L’ancre peut se trouver dans une autre pièce, ou un autre emplacement physique entièrement ; l’ancre ne sera pas locatable tant que l’appareil qui a reçu suffisamment d’informations visuelles sur l’environnement dans lequel l’ancre a été créée, pour restaurer la position de l’ancre par rapport à l’environnement actuel connu. L’implémentation du client doit essayer de localiser l’ancrage par rapport à votre système de coordonnées local ou à votre cadre de référence avant de continuer à essayer de l’utiliser pour du contenu en direct. Par exemple, essayez de localiser l’ancre par rapport à un système de coordonnées actuel régulièrement jusqu’à ce que l’ancre commence à être locatable.
Points particuliers à prendre en compte
L’API TryExportAnchorsAsync permet à plusieurs SpatialAnchors d’être exportés dans le même objet blob binaire opaque. Toutefois, il existe une différence subtile entre les données que l’objet blob inclura, selon qu’un seul SpatialAnchor ou plusieurs SpatialAnchors sont exportés dans un seul appel.
Exportation d’un seul SpatialAnchor
L’objet blob contient une représentation de l’environnement à proximité de SpatialAnchor afin que l’environnement puisse être reconnu sur l’appareil qui importe SpatialAnchor. Une fois l’importation terminée, le nouveau SpatialAnchor sera disponible pour l’appareil. En supposant que l’utilisateur a récemment été à proximité de l’ancre, il sera locatable et les hologrammes attachés à SpatialAnchor peuvent être rendus. Ces hologrammes s’affichent dans le même emplacement physique que sur l’appareil d’origine qui a exporté SpatialAnchor.
Exportation de plusieurs SpatialAnchors
Comme l’exportation d’un seul SpatialAnchor, l’objet blob contient une représentation de l’environnement à proximité de tous les SpatialAnchors spécifiés. En outre, l’objet blob contient des informations sur les connexions entre spatialAnchors inclus, s’ils se trouvent dans le même espace physique. Cela signifie que si deux SpatialAnchors proches sont importés, un hologramme attaché au second SpatialAnchor serait locatable même si l’appareil reconnaît uniquement l’environnement autour du premier SpatialAnchor, car suffisamment de données pour calculer la transformation entre les deux SpatialAnchors a été inclus dans l’objet blob. Si les deux SpatialAnchors ont été exportés individuellement (deux appels distincts à TryExportSpatialAnchors), il se peut qu’il n’y ait pas suffisamment de données incluses dans l’objet blob pour les hologrammes attachés au second SpatialAnchor pour être locatable lorsque le premier est situé.
Exemple : Envoyer des données d’ancre à l’aide d’un objet Windows ::Networking ::StreamSocket
Ici, nous fournissons un exemple d’utilisation des données d’ancrage exportées en l’envoyant sur un réseau TCP. Il s’agit de holographicSpatialAnchorTransferSample.
La classe StreamSocket WinRT utilise la bibliothèque de tâches PPL. Dans le cas d’erreurs réseau, l’erreur est retournée à la tâche suivante de la chaîne à l’aide d’une exception qui est levée à nouveau. L’exception contient un HRESULT indiquant l’état d’erreur.
Utiliser un windows ::Networking ::StreamSocketListener avec TCP pour envoyer des données d’ancre exportées
Créez une instance de serveur qui écoute une connexion.
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");
}
}
Lorsqu’une connexion est reçue, utilisez la connexion de socket client pour envoyer des données d’ancrage.
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());
}
}
À présent, nous pouvons commencer à envoyer un flux de données qui contient les données d’ancre exportées.
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");
}
});
}
Avant de pouvoir envoyer le flux lui-même, nous devons d’abord envoyer un paquet d’en-tête. Ce paquet d’en-tête doit être de longueur fixe, et il doit également indiquer la longueur du tableau variable d’octets qui est le flux de données d’ancre ; dans le cas de cet exemple, nous n’avons pas d’autres données d’en-tête à envoyer. Notre en-tête est donc long de 4 octets et contient un entier non signé 32 bits.
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);
Une fois que la longueur du flux, en octets, a été envoyée au client, nous pouvons continuer à écrire le flux de données lui-même dans le flux de socket. Cela entraîne l’envoi des octets du magasin d’ancres au client.
}).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);
});
}
Comme indiqué précédemment dans cette rubrique, nous devons être prêts à gérer les exceptions contenant des messages d’état d’erreur réseau. Pour les erreurs qui ne sont pas attendues, nous pouvons écrire les informations d’exception dans la console de débogage comme suit. Cela nous donnera un indice sur ce qui s’est passé si notre exemple de code n’est pas en mesure de terminer la connexion, ou s’il n’est pas en mesure d’envoyer les données d’ancrage.
void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Connection error: ") +
exception->ToString()->Data() +
L"\n"
);
}
Utiliser un objet Windows ::Networking ::StreamSocket avec TCP pour recevoir des données d’ancre exportées
Tout d’abord, nous devons nous connecter au serveur. Cet exemple de code montre comment créer et configurer un StreamSocket et créer un DataReader que vous pouvez utiliser pour acquérir des données réseau à l’aide de la connexion de socket.
REMARQUE : Si vous exécutez cet exemple de code, vérifiez que vous configurez et lancez le serveur avant de démarrer le client.
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);
}
}
Une fois que nous avons une connexion, nous pouvons attendre que le serveur envoie des données. Pour ce faire, appelez LoadAsync sur le lecteur de données de flux.
Le premier ensemble d’octets que nous recevons doit toujours être le paquet d’en-tête, qui indique la longueur d’octet du flux de données d’ancre, comme décrit dans la section précédente.
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);
}
Une fois que nous avons reçu le paquet d’en-tête, nous savons combien d’octets de données d’ancre nous devrions nous attendre. Nous pouvons continuer à lire ces octets à partir du flux.
}).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);
}
});
}
Voici notre code pour recevoir le flux de données d’ancrage. Là encore, nous allons d’abord charger les octets du flux ; cette opération peut prendre un certain temps, car StreamSocket attend de recevoir cette quantité d’octets du réseau.
Une fois l’opération de chargement terminée, nous pouvons lire ce nombre d’octets. Si nous avons reçu le nombre d’octets attendus pour le flux de données d’ancre, nous pouvons aller de l’avant et importer les données d’ancre ; si ce n’est pas le cas, il doit y avoir eu une certaine erreur. Par exemple, cela peut se produire lorsque l’instance de serveur se termine avant de pouvoir terminer l’envoi du flux de données, ou que le réseau tombe en panne avant que l’ensemble du flux de données puisse être reçu par le client.
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);
}
}
Là encore, nous devons être prêts à gérer les erreurs réseau inconnues.
void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
std::wstring error = L"Connection error: ";
error += exception->ToString()->Data();
error += L"\n";
OutputDebugString(error.c_str());
}
Et voilà ! Vous devez maintenant disposer d’informations suffisantes pour essayer de localiser les ancres reçues sur le réseau. Là encore, notez que le client doit disposer de suffisamment de données de suivi visuel pour que l’espace localise correctement l’ancre ; s’il ne fonctionne pas immédiatement, essayez de marcher pendant un certain temps. S’il ne fonctionne toujours pas, le serveur envoie plus d’ancres et utilise des communications réseau pour s’entendre sur celui qui fonctionne pour le client. Vous pouvez l’essayer en téléchargeant HolographicSpatialAnchorTransferSample, en configurant vos adresses IP client et serveur, et en la déployant sur des appareils HoloLens clients et serveurs.