Lokale ankeroverdrachten in DirectX
Met lokale ankeroverdrachten kan één HoloLens-apparaat een anker exporteren dat moet worden geïmporteerd door een tweede HoloLens-apparaat.
Notitie
iOS- en Android-apparaten worden niet ondersteund door deze methode.
Notitie
De codefragmenten in dit artikel laten momenteel het gebruik van C++/CX zien in plaats van C++17-compatibele C++/WinRT, zoals wordt gebruikt in de C++ holografische projectsjabloon. De concepten zijn gelijkwaardig voor een C++/WinRT-project, maar u moet de code vertalen.
Ruimtelijke ankers overdragen
U kunt ruimtelijke ankers overbrengen tussen Windows Mixed Reality-apparaten met behulp van SpatialAnchorTransferManager. Met deze API kunt u een anker bundelen met alle ondersteunende sensorgegevens die nodig zijn om die exacte plaats in de wereld te vinden en die bundel vervolgens op een ander apparaat te importeren. Zodra de app op het tweede apparaat dat anker heeft geïmporteerd, kan elke app hologrammen weergeven met behulp van het coördinaatsysteem van dat gedeelde ruimtelijke anker, dat vervolgens op dezelfde plaats in de echte wereld wordt weergegeven.
Houd er rekening mee dat ruimtelijke ankers niet kunnen worden overgedragen tussen verschillende apparaattypen, bijvoorbeeld dat een ruimtelijk HoloLens-anker niet kan worden gegoald met behulp van een insluitende headset. Overgedragen ankers zijn ook niet compatibel met iOS- of Android-apparaten.
Uw app instellen voor het gebruik van de functie spatialPerception
Uw app moet zijn gemachtigd om de SpatialPerception-mogelijkheid te gebruiken voordat deze de SpatialAnchorTransferManager kan gebruiken. Dit is nodig omdat het overdragen van een ruimtelijk anker het delen van sensorafbeeldingen omvat die in de loop van de tijd in de buurt van dat anker zijn verzameld, waaronder mogelijk gevoelige informatie.
Declareer deze mogelijkheid in het bestand package.appxmanifest voor uw app. Hier volgt een voorbeeld:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
De mogelijkheid is afkomstig van de uap2-naamruimte . Als u toegang wilt krijgen tot deze naamruimte in uw manifest, neemt u deze op als een xlmns-kenmerk in het <pakketelement> . Hier volgt een voorbeeld:
<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"
>
OPMERKING: Uw app moet de mogelijkheid tijdens runtime aanvragen voordat deze toegang heeft tot SpatialAnchor-export-/import-API's. Zie RequestAccessAsync in de onderstaande voorbeelden.
Ankergegevens serialiseren door deze te exporteren met SpatialAnchorTransferManager
Een helperfunctie is opgenomen in het codevoorbeeld voor het exporteren (serialiseren) van SpatialAnchor-gegevens . Met deze export-API worden alle ankers in een verzameling sleutel-waardeparen geserialiseerd die tekenreeksen koppelen aan ankers.
// 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
)
{
Eerst moeten we de gegevensstroom instellen. Hierdoor kunnen we 1.) gebruik TryExportAnchorsAsync om de gegevens in een buffer te plaatsen die eigendom is van de app en 2.) gegevens lezen uit de geëxporteerde bytebufferstroom ( een WinRT-gegevensstroom ) in onze eigen geheugenbuffer, een std::vector<byte>.
// 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);
We moeten toestemming vragen om toegang te krijgen tot ruimtelijke gegevens, inclusief ankers die door het systeem worden geëxporteerd.
// 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);
}
});
Als we wel machtigingen krijgen en ankers worden geëxporteerd, kunnen we de gegevensstroom lezen. Hier laten we ook zien hoe u de DataReader en InputStream maakt die we gebruiken om de gegevens te lezen.
// 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);
}
Nadat we bytes uit de stream hebben gelezen, kunnen we ze als volgt opslaan in onze eigen gegevensbuffer.
}).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;
}
});
};
Ankergegevens deserialiseren door deze in het systeem te importeren met behulp van SpatialAnchorTransferManager
Een helperfunctie is opgenomen in het codevoorbeeld om eerder geëxporteerde gegevens te laden. Deze deserialisatiefunctie biedt een verzameling sleutel-waardeparen, vergelijkbaar met wat de SpatialAnchorStore biedt, behalve dat we deze gegevens van een andere bron hebben gekregen, zoals een netwerksocket. U kunt deze gegevens verwerken en redeneren voordat u deze offline opslaat, in-app-geheugen of (indien van toepassing) de SpatialAnchorStore van uw app.
// 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
)
{
Eerst moeten we streamobjecten maken om toegang te krijgen tot de ankergegevens. We schrijven de gegevens van onze buffer naar een systeembuffer, dus maken we een DataWriter die schrijft naar een gegevensstroom in het geheugen om ons doel te bereiken om ankers van een bytebuffer in het systeem te krijgen als 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);
Nogmaals, we moeten ervoor zorgen dat de app gemachtigd is om ruimtelijke ankergegevens te exporteren, waaronder persoonlijke informatie over de omgeving van de gebruiker.
// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
[&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Access is allowed.
Als toegang is toegestaan, kunnen we bytes van de buffer naar een systeemgegevensstroom schrijven.
// 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);
}
Als het opslaan van bytes in de gegevensstroom is gelukt, kunnen we proberen die gegevens te importeren met behulp van 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);
}
Als de gegevens kunnen worden geïmporteerd, krijgen we een kaartweergave van sleutel-waardeparen die tekenreeksen koppelen aan ankers. We kunnen dit laden in onze eigen in-memory gegevensverzameling en die verzameling gebruiken om te zoeken naar ankers die we willen gebruiken.
}).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;
});
}
OPMERKING: Omdat u een anker kunt importeren, betekent dit niet noodzakelijkerwijs dat u het meteen kunt gebruiken. Het anker bevindt zich mogelijk in een andere ruimte of een andere fysieke locatie volledig; het anker kan pas worden verwijderd als het apparaat dat het heeft ontvangen voldoende visuele informatie heeft over de omgeving waarin het anker is gemaakt, om de positie van het anker ten opzichte van de bekende huidige omgeving te herstellen. De client-implementatie moet proberen het anker te vinden ten opzichte van uw lokale coördinatensysteem of referentieframe voordat u verdergaat met het gebruik ervan voor live-inhoud. Probeer bijvoorbeeld regelmatig het anker te lokaliseren ten opzichte van een huidig coördinatensysteem totdat het anker begint te locatable zijn.
Speciale overwegingen
Met de TryExportAnchorsAsync-API kunnen meerdere SpatialAnchors worden geëxporteerd naar dezelfde ondoorzichtige binaire blob. Er is echter een subtiel verschil in welke gegevens de blob bevat, afhankelijk van of één SpatialAnchor of meerdere SpatialAnchors in één aanroep worden geëxporteerd.
Exporteren van één SpatialAnchor
De blob bevat een weergave van de omgeving in de omgeving van SpatialAnchor, zodat de omgeving kan worden herkend op het apparaat dat het SpatialAnchor importeert. Nadat het importeren is voltooid, is het nieuwe SpatialAnchor beschikbaar voor het apparaat. Ervan uitgaande dat de gebruiker onlangs in de buurt van het anker is geweest, zijn deze locatable en hologrammen die aan het SpatialAnchor zijn gekoppeld, kunnen worden weergegeven. Deze hologrammen worden weergegeven op dezelfde fysieke locatie als op het oorspronkelijke apparaat dat het SpatialAnchor heeft geëxporteerd.
Exporteren van meerdere SpatialAnchors
Net als de export van één SpatialAnchor bevat de blob een weergave van de omgeving in de omgeving van alle opgegeven SpatialAnchors. Bovendien bevat de blob informatie over de verbindingen tussen de opgenomen SpatialAnchors, als ze zich in dezelfde fysieke ruimte bevinden. Dit betekent dat als twee nabijgelegen SpatialAnchors worden geïmporteerd, een hologram dat aan het tweede SpatialAnchor is gekoppeld, zelfs als het apparaat alleen de omgeving rond het eerste SpatialAnchor herkent, omdat er voldoende gegevens zijn opgenomen tussen de twee SpatialAnchors in de blob. Als de twee SpatialAnchors afzonderlijk zijn geëxporteerd (twee afzonderlijke aanroepen naar TryExportSpatialAnchors), zijn er mogelijk onvoldoende gegevens opgenomen in de blob voor hologrammen die aan het tweede SpatialAnchor zijn gekoppeld om te locatable wanneer de eerste zich bevindt.
Voorbeeld: Ankergegevens verzenden met behulp van een Windows::Networking::StreamSocket
Hier geven we een voorbeeld van het gebruik van geëxporteerde ankergegevens door deze via een TCP-netwerk te verzenden. Dit is van holographicSpatialAnchorTransferSample.
De WinRT StreamSocket-klasse maakt gebruik van de PPL-taakbibliotheek. In het geval van netwerkfouten wordt de fout geretourneerd naar de volgende taak in de keten met behulp van een uitzondering die opnieuw wordt gegenereerd. De uitzondering bevat een HRESULT die de foutstatus aangeeft.
Een Windows::Networking::StreamSocketListener gebruiken met TCP om geëxporteerde ankergegevens te verzenden
Maak een serverexemplaren die luistert naar een verbinding.
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");
}
}
Wanneer een verbinding wordt ontvangen, gebruikt u de clientsocketverbinding om ankergegevens te verzenden.
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());
}
}
Nu kunnen we beginnen met het verzenden van een gegevensstroom die de geëxporteerde ankergegevens bevat.
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");
}
});
}
Voordat we de stream zelf kunnen verzenden, moeten we eerst een headerpakket verzenden. Dit headerpakket moet een vaste lengte hebben en moet ook de lengte aangeven van de variabele matrix van bytes die de ankergegevensstroom is; in het geval van dit voorbeeld hebben we geen andere headergegevens die moeten worden verzonden, dus onze header is 4 bytes lang en bevat een 32-bits geheel getal zonder teken.
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);
Zodra de lengte van de stroom, in bytes, naar de client is verzonden, kunnen we doorgaan met het schrijven van de gegevensstroom naar de socketstroom. Hierdoor worden bytes van het ankerarchief verzonden naar de 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);
});
}
Zoals eerder in dit onderwerp is vermeld, moeten we voorbereid zijn op het afhandelen van uitzonderingen die netwerkfoutstatusberichten bevatten. Voor fouten die niet worden verwacht, kunnen we de uitzonderingsgegevens zo schrijven naar de foutopsporingsconsole. Dit geeft ons een idee van wat er is gebeurd als het codevoorbeeld de verbinding niet kan voltooien of als het verzenden van de ankergegevens niet kan worden voltooid.
void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Connection error: ") +
exception->ToString()->Data() +
L"\n"
);
}
Een Windows::Networking::StreamSocket gebruiken met TCP om geëxporteerde ankergegevens te ontvangen
Eerst moeten we verbinding maken met de server. In dit codevoorbeeld ziet u hoe u een StreamSocket maakt en configureert en hoe u een DataReader maakt die u kunt gebruiken om netwerkgegevens te verkrijgen met behulp van de socketverbinding.
OPMERKING: Als u deze voorbeeldcode uitvoert, moet u ervoor zorgen dat u de server configureert en start voordat u de client start.
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);
}
}
Zodra er een verbinding is, kunnen we wachten totdat de server gegevens verzendt. We doen dit door LoadAsync aan te roepen op de gegevenslezer van de stream.
De eerste set bytes die we ontvangen, moet altijd het headerpakket zijn, wat de bytelengte van de ankergegevensstroom aangeeft, zoals beschreven in de vorige sectie.
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);
}
Nadat we het headerpakket hebben ontvangen, weten we hoeveel bytes ankergegevens we moeten verwachten. We kunnen doorgaan met het lezen van die bytes uit de stream.
}).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);
}
});
}
Hier volgt onze code voor het ontvangen van de ankergegevensstroom. Opnieuw laden we eerst de bytes uit de stream; Het kan enige tijd duren voordat deze bewerking is voltooid wanneer de StreamSocket wacht om die hoeveelheid bytes van het netwerk te ontvangen.
Wanneer de laadbewerking is voltooid, kunnen we dat aantal bytes lezen. Als we het aantal bytes hebben ontvangen dat we verwachten voor de ankergegevensstroom, kunnen we doorgaan en de ankergegevens importeren; Zo niet, dan moet er een soort fout zijn opgetreden. Dit kan bijvoorbeeld gebeuren wanneer het serverexemplaren worden beëindigd voordat het verzenden van de gegevensstroom kan worden voltooid of als het netwerk uitvalt voordat de volledige gegevensstroom door de client kan worden ontvangen.
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);
}
}
Ook hier moeten we voorbereid zijn op het afhandelen van onbekende netwerkfouten.
void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
std::wstring error = L"Connection error: ";
error += exception->ToString()->Data();
error += L"\n";
OutputDebugString(error.c_str());
}
Dat is het! U moet nu voldoende informatie hebben om te proberen de ankers te vinden die via het netwerk zijn ontvangen. Houd er nogmaals rekening mee dat de client voldoende visuele traceringsgegevens moet hebben voor de ruimte om het anker te vinden; Als het niet meteen werkt, kunt u even rondlopen. Als het nog steeds niet werkt, laat u de server meer ankers verzenden en gebruikt u netwerkcommunicatie om akkoord te gaan met een die werkt voor de client. U kunt dit proberen door de HolographicSpatialAnchorTransferSample te downloaden, uw client- en server-IP-adressen te configureren en te implementeren op holoLens-apparaten met client en server.