Přenosy místních ukotvení v DirectX
Přenosy místních ukotvení umožňují jednomu zařízení HoloLens exportovat ukotvení, které se má importovat pomocí druhého zařízení HoloLens.
Poznámka:
Tento přístup nepodporuje zařízení s iOSem a Androidem.
Poznámka:
Fragmenty kódu v tomto článku aktuálně demonstrují použití C++/CX místo C++17 kompatibilních s C++/WinRT, jak se používá v šabloně holografického projektu C++. Koncepty jsou ekvivalentní pro projekt C++/WinRT, i když budete muset kód přeložit.
Přenos prostorových ukotvení
Prostorové kotvy můžete přenášet mezi zařízeními s Windows Mixed Reality pomocí spatialAnchorTransferManager. Toto rozhraní API umožňuje seskupit kotvu se všemi podpůrnými daty snímačů potřebnými k nalezení tohoto přesného místa na světě a následným importem této sady do jiného zařízení. Jakmile aplikace na druhém zařízení tuto ukotvení naimportuje, může každá aplikace vykreslit hologramy pomocí souřadnicového systému sdíleného prostorového ukotvení, které se pak zobrazí na stejném místě ve skutečném světě.
Všimněte si, že prostorové kotvy nemohou přenášet mezi různými typy zařízení, například prostorové ukotvení HoloLens nemusí být lokovatelné pomocí imerzivní náhlavní soupravy. Přenášené kotvy také nejsou kompatibilní se zařízeními s iOSem nebo Androidem.
Nastavení aplikace tak, aby používala funkci spatialPerception
Aplikace musí být udělena oprávnění k používání funkce SpatialPerception, aby bylo možné použít SpatialAnchorTransferManager. To je nezbytné, protože přenos prostorové kotvy zahrnuje sdílení obrázků snímačů shromážděných v průběhu času v blízkosti tohoto ukotvení, což může zahrnovat citlivé informace.
Deklarujte tuto funkci v souboru package.appxmanifest pro vaši aplikaci. Tady je příklad:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
Funkce pochází z oboru názvů uap2 . Pokud chcete získat přístup k tomuto oboru názvů v manifestu, zahrňte ho jako atribut xlmns do elementu <Package> . Tady je příklad:
<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"
>
POZNÁMKA: Vaše aplikace bude muset požádat o možnost za běhu, než bude mít přístup k rozhraním API pro export a import spatialAnchor. Viz RequestAccessAsync v následujících příkladech.
Serializace ukotvení dat exportem pomocí SpatialAnchorTransferManager
Pomocná funkce je součástí ukázky kódu pro export (serializace) spatialAnchor dat. Toto rozhraní API pro export serializuje všechna ukotvení v kolekci párů klíč-hodnota asociující řetězce s kotvami.
// 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
)
{
Nejprve musíme nastavit datový proud. To nám umožní 1.) Použijte TryExportAnchorsAsync k vložení dat do vyrovnávací paměti vlastněné aplikací a 2.) čtení dat z exportovaného bajtového vyrovnávacího proudu – což je datový proud WinRT – do vlastní vyrovnávací paměti, což je bajt> 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);
Potřebujeme požádat o oprávnění pro přístup k prostorovými datům, včetně ukotvení exportovaných systémem.
// 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);
}
});
Pokud získáme oprávnění a ukotvení se exportují, můžeme datový stream číst. Zde také ukážeme, jak vytvořit DataReader a InputStream, které použijeme ke čtení dat.
// Get the input stream for the anchor byte stream.
IInputStream^ inputStream = stream->GetInputStreamAt(0);
// Create a DataReader, to get bytes from the anchor byte stream.
DataReader^ reader = ref new DataReader(inputStream);
return accessRequestedTask.then([anchorByteDataOut, stream, reader](bool nchorsExported)
{
if (anchorsExported)
{
// Get the size of the exported anchor byte stream.
size_t bufferSize = static_cast<size_t>(stream->Size);
// Resize the output buffer to accept the data from the stream.
anchorByteDataOut->reserve(bufferSize);
anchorByteDataOut->resize(bufferSize);
// Read the exported anchor store into the stream.
return create_task(reader->LoadAsync(bufferSize));
}
else
{
return task_from_result<size_t>(0);
}
Po přečtení bajtů z datového proudu je můžeme uložit do vlastní vyrovnávací paměti dat, jako je tomu tak.
}).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;
}
});
};
Deserializace dat ukotvení importem do systému pomocí SpatialAnchorTransferManager
Pomocná funkce je součástí ukázky kódu pro načtení dříve exportovaných dat. Tato deserializační funkce poskytuje kolekci párů klíč-hodnota, podobně jako spatialAnchorStore – s výjimkou toho, že jsme tato data získali z jiného zdroje, například ze síťového soketu. Před uložením těchto dat do režimu offline, pomocí paměti v aplikaci nebo (pokud je to možné) aplikace SpatialAnchorStore můžete zpracovat a zdůvodnět.
// 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
)
{
Nejprve potřebujeme vytvořit objekty streamu pro přístup k datům ukotvení. Budeme zapisovat data z naší vyrovnávací paměti do systémové vyrovnávací paměti, takže vytvoříme DataWriter, který zapisuje do datového proudu v paměti, abychom dosáhli našeho cíle získání ukotvení z bajtové vyrovnávací paměti do systému jako SpatialAnchors.
// Create a random access stream for the anchor data.
InMemoryRandomAccessStream^ stream = ref new InMemoryRandomAccessStream();
// Get an output stream for the anchor data.
IOutputStream^ outputStream = stream->GetOutputStreamAt(0);
// Create a writer, to put the bytes in the stream.
DataWriter^ writer = ref new DataWriter(outputStream);
Znovu potřebujeme zajistit, aby aplikace má oprávnění k exportu prostorových ukotvených dat, což by mohlo zahrnovat soukromé informace o prostředí uživatele.
// Request access to transfer spatial anchors.
return create_task(SpatialAnchorTransferManager::RequestAccessAsync()).then(
[&anchorByteDataIn, writer](SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Access is allowed.
Pokud je přístup povolený, můžeme zapisovat bajty z vyrovnávací paměti do systémového datového proudu.
// 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);
}
Pokud jsme úspěšně ukládali bajty do datového proudu, můžeme se pokusit tato data importovat pomocí 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);
}
Pokud se data dají importovat, získáme mapové zobrazení párů klíč-hodnota, které přidruží řetězce k ukotvením. Můžeme to načíst do vlastní kolekce dat v paměti a tuto kolekci použít k vyhledání ukotvení, které nás zajímají.
}).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;
});
}
POZNÁMKA: Jen proto, že můžete importovat ukotvení, nemusí nutně znamenat, že ho můžete okamžitě použít. Kotva může být v jiné místnosti nebo zcela v jiném fyzickém umístění; ukotvení nebude lokovatelné, dokud zařízení, které obdrželo, nemá dostatek vizuálních informací o prostředí, ve které bylo ukotveno, aby se obnovila pozice ukotvení vzhledem ke známému aktuálnímu prostředí. Než budete pokračovat v pokusu o použití živého obsahu, měla by implementace klienta zkusit najít ukotvení vzhledem k místnímu souřadnicovém systému nebo referenčnímu rámečku. Zkuste například vyhledat ukotvení vzhledem k aktuálnímu souřadnicovém systému pravidelně, dokud se ukotvení nezačne lokovat.
Zvláštní aspekty
Rozhraní API TryExportAnchorsAsync umožňuje export více prostorovýchanchorů do stejného neprůhledného binárního objektu blob. V datech, která objekt blob bude obsahovat, je však malý rozdíl v tom, jestli se jeden prostorovýanchor nebo více prostorovýchanchorů exportuje do jednoho volání.
Export jednoho spatialAnchoru
Objekt blob obsahuje reprezentaci prostředí v blízkosti SpatialAnchor, aby bylo možné prostředí rozpoznat na zařízení, které importuje SpatialAnchor. Po dokončení importu bude pro zařízení k dispozici nový SpatialAnchor. Za předpokladu, že uživatel byl nedávno v blízkosti ukotvení, bude lokatable a hologramy připojené k SpatialAnchor lze vykreslit. Tyto hologramy se zobrazí ve stejném fyzickém umístění jako na původním zařízení, které exportovaly SpatialAnchor.
Export více prostorovýchanchorů
Podobně jako export jednoho SpatialAnchoru obsahuje objekt blob reprezentaci prostředí v blízkosti všech zadaných prostorovýchanchorů. Kromě toho objekt blob obsahuje informace o připojeních mezi zahrnutými SpatialAnchors, pokud jsou umístěny ve stejném fyzickém prostoru. To znamená, že pokud se naimportují dva blízké SpatialAnchors, pak by byl hologram připojený k druhému SpatialAnchoru lokalizovatelný i v případě, že zařízení rozpozná prostředí pouze kolem prvního SpatialAnchoru, protože do objektu blob byla zahrnuta dostatek dat pro výpočet transformace mezi dvěma SpatialAnchors. Pokud byly dva SpatialAnchors exportovány jednotlivě (dvě samostatná volání TryExportSpatialAnchors), nemusí existovat dostatek dat zahrnutých do objektu blob pro hologramy připojené k druhému SpatialAnchoru, aby byly lokovatelné při prvním umístění.
Příklad: Odeslání dat ukotvení pomocí Windows::Networking::StreamSocket
Tady uvádíme příklad použití exportovaných ukotvených dat jejich odesláním přes síť TCP. Pochází z HolographicSpatialAnchorTransferSample.
Třída WinRT StreamSocket používá knihovnu úloh PPL. V případě chyb sítě se chyba vrátí do dalšího úkolu v řetězu pomocí výjimky, která se znovu vyvolá. Výjimka obsahuje HRESULT označující stav chyby.
Použití windows::Networking::StreamSocketListener s protokolem TCP k odesílání exportovaných ukotvených dat
Vytvořte instanci serveru, která naslouchá připojení.
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");
}
}
Při přijetí připojení použijte připojení klientského soketu k odesílání dat ukotvení.
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());
}
}
Teď můžeme začít odesílat datový proud, který obsahuje exportovaná ukotvená data.
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");
}
});
}
Před odesláním samotného datového proudu musíme nejprve odeslat paket hlavičky. Tento paket hlavičky musí mít pevnou délku a musí také označit délku proměnné pole bajtů, které je datový proud ukotvení; v případě tohoto příkladu nemáme žádná další data záhlaví, která se mají odeslat, takže naše hlavička je 4 bajty dlouhá a obsahuje 32bitové celé číslo bez znaménka.
Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataLengthMessage(size_t dataStreamLength)
{
unsigned int arrayLength = dataStreamLength;
byte* data = reinterpret_cast<byte*>(&arrayLength);
m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
return create_task(m_anchorTcpSocketStreamWriter->StoreAsync()).then([this](unsigned int bytesStored)
{
if (bytesStored > 0)
{
OutputDebugString(L"Anchor data length stored in stream; Flushing stream.\n");
return create_task(m_anchorTcpSocketStreamWriter->FlushAsync());
}
else
{
OutputDebugString(L"Error: Anchor data length not stored in stream.\n");
return task_from_result<bool>(false);
}
});
}
Concurrency::task<bool> SampleAnchorTcpServer::SendAnchorDataStreamIMap<String^, SpatialAnchor^>^ anchorsToSend)
{
return SpatialAnchorImportExportHelper::ExportAnchorDataAsync(
&m_exportedAnchorStoreBytes,
anchorsToSend
).then([this](bool anchorDataExported)
{
if (anchorDataExported)
{
const size_t arrayLength = m_exportedAnchorStoreBytes.size();
if (arrayLength > 0)
{
OutputDebugString(L"Anchor data was exported; sending data stream length message.\n");
return SendAnchorDataLengthMessage(arrayLength);
}
}
OutputDebugString(L"Error: Anchor data was not exported.\n");
// No data to send.
return task_from_result<bool>(false);
Po odeslání délky datového proudu v bajtech do klienta můžeme pokračovat v zápisu samotného datového proudu do streamu soketu. To způsobí, že se bajty úložiště ukotvení odešlou klientovi.
}).then([this](bool dataLengthSent)
{
if (dataLengthSent)
{
OutputDebugString(L"Data stream length message sent; writing exported anchor store bytes to stream.\n");
m_anchorTcpSocketStreamWriter->WriteBytes(Platform::ArrayReference<byte>(&m_exportedAnchorStoreBytes[0], m_exportedAnchorStoreBytes.size()));
return create_task(m_anchorTcpSocketStreamWriter->StoreAsync());
}
else
{
OutputDebugString(L"Error: Data stream length message not sent.\n");
return task_from_result<size_t>(0);
}
}).then([this](unsigned int bytesStored)
{
if (bytesStored > 0)
{
PrintWstringToDebugConsole(
std::to_wstring(bytesStored) +
L" bytes of anchor data written and stored to stream; flushing stream.\n"
);
}
else
{
OutputDebugString(L"Error: No anchor data bytes were written to the stream.\n");
}
return task_from_result<bool>(false);
});
}
Jak jsme si poznamenali dříve v tomto tématu, musíme být připraveni zpracovat výjimky obsahující chybové zprávy sítě. V případě chyb, které nejsou očekávané, můžeme napsat informace o výjimce do konzoly ladění, jako je tomu tak. To nám poskytne povědomí o tom, co se stalo, pokud náš vzorový kód nemůže dokončit připojení nebo pokud nemůže dokončit odesílání dat ukotvení.
void SampleAnchorTcpServer::HandleException(Exception^ exception)
{
PrintWstringToDebugConsole(
std::wstring(L"Connection error: ") +
exception->ToString()->Data() +
L"\n"
);
}
Použití Windows::Networking::StreamSocket s protokolem TCP k příjmu exportovaných dat ukotvení
Nejprve se musíme připojit k serveru. Tento ukázkový kód ukazuje, jak vytvořit a nakonfigurovat StreamSocket a vytvořit DataReader, který můžete použít k získání síťových dat pomocí připojení soketu.
POZNÁMKA: Pokud spustíte tento ukázkový kód, před spuštěním klienta se ujistěte, že nakonfigurujete a spustíte server.
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);
}
}
Jakmile máme připojení, můžeme počkat, až server odešle data. Provedeme to voláním LoadAsync pro čtečku dat streamu.
První sada bajtů, které obdržíme, by měla vždy být paket hlavičky, který označuje délku bajtu datového proudu ukotvení, jak je popsáno v předchozí části.
void SampleAnchorTcpClient::WaitForAnchorDataStream()
{
if (m_anchorTcpSocketStreamReader == nullptr)
{
// We have not connected yet.
return;
}
OutputDebugString(L"Waiting for server message.\n");
// Wait for the first message, which specifies the byte length of the string data.
create_task(m_anchorTcpSocketStreamReader->LoadAsync(SampleAnchorTcpCommon::c_streamHeaderByteArrayLength)).then([this](unsigned int numberOfBytes)
{
if (numberOfBytes > 0)
{
OutputDebugString(L"Server message incoming.\n");
return ReceiveAnchorDataLengthMessage();
}
else
{
OutputDebugString(L"0-byte async task received, awaiting server message again.\n");
WaitForAnchorDataStream();
return task_from_result<size_t>(0);
}
...
task<size_t> SampleAnchorTcpClient::ReceiveAnchorDataLengthMessage()
{
byte data[4];
m_anchorTcpSocketStreamReader->ReadBytes(Platform::ArrayReference<byte>(data, SampleAnchorTcpCommon::c_streamHeaderByteArrayLength));
unsigned int lengthMessageSize = *reinterpret_cast<unsigned int*>(data);
if (lengthMessageSize > 0)
{
OutputDebugString(L"One or more anchors to be received.\n");
return task_from_result<size_t>(lengthMessageSize);
}
else
{
OutputDebugString(L"No anchors to be received.\n");
ConnectToServer();
}
return task_from_result<size_t>(0);
}
Po přijetí paketu hlavičky víme, kolik bajtů dat ukotvení bychom měli očekávat. Můžeme pokračovat ve čtení těchto bajtů z datového proudu.
}).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);
}
});
}
Tady je náš kód pro příjem datového proudu ukotvení. Znovu načteme bajty z datového proudu; dokončení této operace může nějakou dobu trvat, protože StreamSocket čeká na příjem tohoto počtu bajtů ze sítě.
Po dokončení operace načítání můžeme tento počet bajtů přečíst. Pokud jsme obdrželi počet bajtů, které očekáváme pro datový proud ukotvení, můžeme pokračovat a importovat data ukotvení; pokud ne, muselo dojít k nějaké chybě. K tomu může dojít například v případě, že se instance serveru ukončí předtím, než dokončí odesílání datového proudu, nebo se síť ukončí, než klient obdrží celý datový proud.
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);
}
}
Opět musíme být připraveni na zpracování neznámých chyb sítě.
void SampleAnchorTcpClient::HandleException(Exception^ exception)
{
std::wstring error = L"Connection error: ";
error += exception->ToString()->Data();
error += L"\n";
OutputDebugString(error.c_str());
}
A je to! Teď byste měli mít dostatek informací, abyste zkusili vyhledat ukotvení přijatá přes síť. Znovu si všimněte, že klient musí mít dostatek vizuálních dat pro sledování místa pro úspěšné vyhledání ukotvení; pokud to nefunguje hned, zkuste chvíli jít kolem. Pokud to stále nefunguje, požádejte server, aby odeslal další kotvy a pomocí síťové komunikace se dohodl na tom, který funguje pro klienta. Můžete to vyzkoušet tak, že si stáhnete HolographicSpatialAnchorTransferSample, nakonfigurujete IP adresy klienta a serveru a nasadíte ho na zařízení s klientem a serverem HoloLens.