From 5443dd2ffa7f1c667a0b2e02fb0fbcbd8772beb1 Mon Sep 17 00:00:00 2001 From: osa Date: Fri, 26 Mar 2021 15:02:41 +0300 Subject: [PATCH] 0032248: Visualization - load "false" deferred glTF data immediately --- src/Poly/Poly_Triangulation.cxx | 4 +- src/RWGltf/RWGltf_CafReader.cxx | 140 ++++++++++++++----- src/RWGltf/RWGltf_CafReader.hxx | 4 +- src/RWGltf/RWGltf_GltfLatePrimitiveArray.cxx | 21 +++ src/RWGltf/RWGltf_GltfLatePrimitiveArray.hxx | 5 +- src/RWGltf/RWGltf_TriangulationReader.cxx | 119 ++++++++++++++-- src/RWGltf/RWGltf_TriangulationReader.hxx | 53 ++++++- tests/de_mesh/gltf_lateload/helmet | 12 ++ 8 files changed, 295 insertions(+), 63 deletions(-) create mode 100644 tests/de_mesh/gltf_lateload/helmet diff --git a/src/Poly/Poly_Triangulation.cxx b/src/Poly/Poly_Triangulation.cxx index 95f369ee62..62e3aef70b 100644 --- a/src/Poly/Poly_Triangulation.cxx +++ b/src/Poly/Poly_Triangulation.cxx @@ -542,11 +542,11 @@ Handle(Poly_Triangulation) Poly_Triangulation::DetachedLoadDeferredData (const H return Handle(Poly_Triangulation)(); } Handle(Poly_Triangulation) aResult = createNewEntity(); - if (!loadDeferredData(theFileSystem, aResult)) + if (!loadDeferredData (theFileSystem, aResult)) { return Handle(Poly_Triangulation)(); } - aResult->SetMeshPurpose(aResult->MeshPurpose() | Poly_MeshPurpose_Loaded); + aResult->SetMeshPurpose (aResult->MeshPurpose() | Poly_MeshPurpose_Loaded); return aResult; } diff --git a/src/RWGltf/RWGltf_CafReader.cxx b/src/RWGltf/RWGltf_CafReader.cxx index 05bef91d63..90f1b01736 100644 --- a/src/RWGltf/RWGltf_CafReader.cxx +++ b/src/RWGltf/RWGltf_CafReader.cxx @@ -30,28 +30,18 @@ IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafReader, RWMesh_CafReader) -//! Functor for parallel execution. -class RWGltf_CafReader::CafReader_GltfReaderFunctor +//! Abstract base functor for parallel execution of glTF data loading. +class RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor { public: - struct GltfReaderTLS - { - Handle(OSD_FileSystem) FileSystem; - }; - //! Main constructor. - CafReader_GltfReaderFunctor (RWGltf_CafReader* myCafReader, - NCollection_Vector& theFaceList, - const Message_ProgressRange& theProgress, - const OSD_ThreadPool::Launcher& theThreadPool, - const TCollection_AsciiString& theErrPrefix) - : myCafReader (myCafReader), - myFaceList (&theFaceList), - myErrPrefix (theErrPrefix), + CafReader_GltfBaseLoadingFunctor (NCollection_Vector& theFaceList, + const Message_ProgressRange& theProgress, + const OSD_ThreadPool::Launcher& theThreadPool) + : myFaceList (&theFaceList), myProgress (theProgress, "Loading glTF triangulation", Max (1, theFaceList.Size())), - myThreadPool(theThreadPool), - myTlsData (theThreadPool.LowerThreadIndex(), theThreadPool.UpperThreadIndex()) + myThreadPool(theThreadPool) { // } @@ -60,26 +50,15 @@ public: void operator() (int theThreadIndex, int theFaceIndex) const { - GltfReaderTLS& aTlsData = myTlsData.ChangeValue (theThreadIndex); - if (aTlsData.FileSystem.IsNull()) - { - aTlsData.FileSystem = new OSD_CachedFileSystem(); - } - TopLoc_Location aDummyLoc; TopoDS_Face& aFace = myFaceList->ChangeValue (theFaceIndex); Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (aFace, aDummyLoc)); - if (myCafReader->ToKeepLateData()) + Handle(Poly_Triangulation) aPolyData = loadData (aLateData, theThreadIndex); + if (!aPolyData.IsNull()) { - aLateData->LoadDeferredData (aTlsData.FileSystem); - } - else - { - Handle(Poly_Triangulation) aPolyData = aLateData->DetachedLoadDeferredData (aTlsData.FileSystem); BRep_Builder aBuilder; aBuilder.UpdateFace (aFace, aPolyData); // replace all "proxy"-triangulations of face by loaded active one. } - if (myThreadPool.HasThreads()) { Standard_Mutex::Sentry aLock (&myMutex); @@ -91,17 +70,97 @@ public: } } +protected: + + //! Load primitive array. + virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData, + int theThreadIndex) const = 0; + +protected: + + NCollection_Vector* myFaceList; + mutable Standard_Mutex myMutex; + mutable Message_ProgressScope myProgress; + const OSD_ThreadPool::Launcher& myThreadPool; +}; +//! Functor for parallel execution of all glTF data loading. +class RWGltf_CafReader::CafReader_GltfFullDataLoadingFunctor : public RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor +{ +public: + + struct GltfReaderTLS + { + Handle(OSD_FileSystem) FileSystem; + }; + + //! Main constructor. + CafReader_GltfFullDataLoadingFunctor (RWGltf_CafReader* myCafReader, + NCollection_Vector& theFaceList, + const Message_ProgressRange& theProgress, + const OSD_ThreadPool::Launcher& theThreadPool) + : CafReader_GltfBaseLoadingFunctor (theFaceList, theProgress, theThreadPool), + myCafReader (myCafReader), + myTlsData (theThreadPool.LowerThreadIndex(), theThreadPool.UpperThreadIndex()) + { + // + } + +protected: + + //! Load primitive array. + virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData, + int theThreadIndex) const Standard_OVERRIDE + { + GltfReaderTLS& aTlsData = myTlsData.ChangeValue (theThreadIndex); + if (aTlsData.FileSystem.IsNull()) + { + aTlsData.FileSystem = new OSD_CachedFileSystem(); + } + // Load stream data if exists + if (Handle(Poly_Triangulation) aStreamLoadedData = theLateData->LoadStreamData()) + { + return aStreamLoadedData; + } + // Load file data + if (myCafReader->ToKeepLateData()) + { + theLateData->LoadDeferredData (aTlsData.FileSystem); + return Handle(Poly_Triangulation)(); + } + return theLateData->DetachedLoadDeferredData (aTlsData.FileSystem); + } + private: RWGltf_CafReader* myCafReader; - NCollection_Vector* myFaceList; - TCollection_AsciiString myErrPrefix; - mutable Standard_Mutex myMutex; - mutable Message_ProgressScope myProgress; - const OSD_ThreadPool::Launcher& myThreadPool; mutable NCollection_Array1 myTlsData; }; +//! Functor for parallel execution of loading of only glTF data saved in stream buffers. +class RWGltf_CafReader::CafReader_GltfStreamDataLoadingFunctor : public RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor +{ +public: + + //! Main constructor. + CafReader_GltfStreamDataLoadingFunctor (NCollection_Vector& theFaceList, + const Message_ProgressRange& theProgress, + const OSD_ThreadPool::Launcher& theThreadPool) + : CafReader_GltfBaseLoadingFunctor (theFaceList, theProgress, theThreadPool) + { + // + } + +protected: + + //! Load primitive array. + virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData, + int theThreadIndex) const Standard_OVERRIDE + { + (void )theThreadIndex; + return theLateData->LoadStreamData(); + } +}; + //================================================================ // Function : Constructor // Purpose : @@ -306,8 +365,16 @@ Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector Handle(RWGltf_TriangulationReader) aReader = Handle(RWGltf_TriangulationReader)::DownCast(createMeshReaderContext()); aReader->SetFileName (theFile); updateLateDataReader (theFaces, aReader); + if (myToSkipLateDataLoading) { + // Load glTF data encoded in base64. It should not be skipped and saved in "proxy" object to be loaded later. + const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool(); + const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1; + OSD_ThreadPool::Launcher aLauncher(*aThreadPool, aNbThreads); + CafReader_GltfStreamDataLoadingFunctor aFunctor(theFaces, theProgress, aLauncher); + aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor); + return Standard_True; } @@ -317,8 +384,7 @@ Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1; OSD_ThreadPool::Launcher aLauncher (*aThreadPool, aNbThreads); - CafReader_GltfReaderFunctor aFunctor (this, theFaces, theProgress, aLauncher, - TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n"); + CafReader_GltfFullDataLoadingFunctor aFunctor (this, theFaces, theProgress, aLauncher); aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor); aReader->PrintStatistic(); diff --git a/src/RWGltf/RWGltf_CafReader.hxx b/src/RWGltf/RWGltf_CafReader.hxx index b99db7c85c..6d36cb7504 100644 --- a/src/RWGltf/RWGltf_CafReader.hxx +++ b/src/RWGltf/RWGltf_CafReader.hxx @@ -98,7 +98,9 @@ protected: protected: - class CafReader_GltfReaderFunctor; + class CafReader_GltfBaseLoadingFunctor; + class CafReader_GltfFullDataLoadingFunctor; + class CafReader_GltfStreamDataLoadingFunctor; protected: diff --git a/src/RWGltf/RWGltf_GltfLatePrimitiveArray.cxx b/src/RWGltf/RWGltf_GltfLatePrimitiveArray.cxx index 8ffadd442d..ebca9dc366 100644 --- a/src/RWGltf/RWGltf_GltfLatePrimitiveArray.cxx +++ b/src/RWGltf/RWGltf_GltfLatePrimitiveArray.cxx @@ -17,6 +17,7 @@ #include #include #include +#include IMPLEMENT_STANDARD_RTTIEXT(RWGltf_GltfLatePrimitiveArray, RWMesh_TriangulationSource) @@ -90,3 +91,23 @@ RWGltf_GltfPrimArrayData& RWGltf_GltfLatePrimitiveArray::AddPrimArrayData (RWGlt return myData.ChangeLast(); } } + +//======================================================================= +//function : LoadStreamData +//purpose : +//======================================================================= +Handle(Poly_Triangulation) RWGltf_GltfLatePrimitiveArray::LoadStreamData() const +{ + Handle(RWGltf_TriangulationReader) aGltfReader = Handle(RWGltf_TriangulationReader)::DownCast(myReader); + if (aGltfReader.IsNull()) + { + return Handle(Poly_Triangulation)(); + } + Handle(Poly_Triangulation) aResult = createNewEntity(); + if (!aGltfReader->LoadStreamData (this, aResult)) + { + return Handle(Poly_Triangulation)(); + } + aResult->SetMeshPurpose (aResult->MeshPurpose() | Poly_MeshPurpose_Loaded); + return aResult; +} diff --git a/src/RWGltf/RWGltf_GltfLatePrimitiveArray.hxx b/src/RWGltf/RWGltf_GltfLatePrimitiveArray.hxx index a06586fee1..1c39f4106a 100644 --- a/src/RWGltf/RWGltf_GltfLatePrimitiveArray.hxx +++ b/src/RWGltf/RWGltf_GltfLatePrimitiveArray.hxx @@ -78,13 +78,16 @@ public: //! Add primitive array data element. Standard_EXPORT RWGltf_GltfPrimArrayData& AddPrimArrayData (RWGltf_GltfArrayType theType); - //! Returns TRUE if there is deferred storege and some triangulation data + //! Return TRUE if there is deferred storege and some triangulation data //! that can be loaded using LoadDeferredData(). virtual Standard_Boolean HasDeferredData() const Standard_OVERRIDE { return !myData.IsEmpty() && RWMesh_TriangulationSource::HasDeferredData(); } + //! Load primitive array saved as stream buffer to new triangulation object. + Standard_EXPORT Handle(Poly_Triangulation) LoadStreamData() const; + protected: NCollection_Sequence myData; diff --git a/src/RWGltf/RWGltf_TriangulationReader.cxx b/src/RWGltf/RWGltf_TriangulationReader.cxx index 169b76a643..09fbf6d3e0 100644 --- a/src/RWGltf/RWGltf_TriangulationReader.cxx +++ b/src/RWGltf/RWGltf_TriangulationReader.cxx @@ -48,6 +48,107 @@ void RWGltf_TriangulationReader::reportError (const TCollection_AsciiString& the Message::SendFail (TCollection_AsciiString("File '") + myFileName + "' defines invalid glTF!\n" + theText); } +// ======================================================================= +// function : LoadStreamData +// purpose : +// ======================================================================= +bool RWGltf_TriangulationReader::LoadStreamData (const Handle(RWMesh_TriangulationSource)& theSourceMesh, + const Handle(Poly_Triangulation)& theDestMesh) const +{ + Standard_ASSERT_RETURN (!theDestMesh.IsNull(), "The destination mesh should be initialized before loading data to it", false); + theDestMesh->Clear(); + theDestMesh->SetDoublePrecision (myIsDoublePrecision); + + if (!loadStreamData (theSourceMesh, theDestMesh)) + { + theDestMesh->Clear(); + return false; + } + if (!finalizeLoading (theSourceMesh, theDestMesh)) + { + theDestMesh->Clear(); + return false; + } + return true; +} + +// ======================================================================= +// function : readStreamData +// purpose : +// ======================================================================= +bool RWGltf_TriangulationReader::readStreamData (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceGltfMesh, + const RWGltf_GltfPrimArrayData& theGltfData, + const Handle(Poly_Triangulation)& theDestMesh) const +{ + Standard_ArrayStreamBuffer aStreamBuffer ((const char* )theGltfData.StreamData->Data(), theGltfData.StreamData->Size()); + std::istream aStream (&aStreamBuffer); + aStream.seekg ((std::streamoff )theGltfData.StreamOffset, std::ios_base::beg); + if (!readBuffer (theSourceGltfMesh, theDestMesh, aStream, theGltfData.Accessor, theGltfData.Type)) + { + return false; + } + return true; +} + +// ======================================================================= +// function : readFileData +// purpose : +// ======================================================================= +bool RWGltf_TriangulationReader::readFileData (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceGltfMesh, + const RWGltf_GltfPrimArrayData& theGltfData, + const Handle(Poly_Triangulation)& theDestMesh, + const Handle(OSD_FileSystem)& theFileSystem) const +{ + const Handle(OSD_FileSystem)& aFileSystem = !theFileSystem.IsNull() ? theFileSystem : OSD_FileSystem::DefaultFileSystem(); + opencascade::std::shared_ptr aSharedStream = aFileSystem->OpenIStream + (theGltfData.StreamUri, std::ios::in | std::ios::binary, theGltfData.StreamOffset); + if (aSharedStream.get() == NULL) + { + reportError (TCollection_AsciiString("Buffer '") + theSourceGltfMesh->Id() + "refers to invalid file '" + theGltfData.StreamUri + "'."); + return false; + } + if (!readBuffer (theSourceGltfMesh, theDestMesh, *aSharedStream.get(), theGltfData.Accessor, theGltfData.Type)) + { + return false; + } + return true; +} + +// ======================================================================= +// function : loadStreamData +// purpose : +// ======================================================================= +bool RWGltf_TriangulationReader::loadStreamData (const Handle(RWMesh_TriangulationSource)& theSourceMesh, + const Handle(Poly_Triangulation)& theDestMesh, + bool theToResetStream) const +{ + const Handle(RWGltf_GltfLatePrimitiveArray) aSourceGltfMesh = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast(theSourceMesh); + if (aSourceGltfMesh.IsNull() + || aSourceGltfMesh->PrimitiveMode() == RWGltf_GltfPrimitiveMode_UNKNOWN) + { + return false; + } + bool wasLoaded = false; + for (NCollection_Sequence::Iterator aDataIter (aSourceGltfMesh->Data()); aDataIter.More(); aDataIter.Next()) + { + RWGltf_GltfPrimArrayData& aData = aDataIter.ChangeValue(); + if (aData.StreamData.IsNull()) + { + continue; + } + if (!readStreamData (aSourceGltfMesh, aData, theDestMesh)) + { + return false; + } + if (theToResetStream) + { + aData.StreamData.Nullify(); + } + wasLoaded = true; + } + return wasLoaded; +} + // ======================================================================= // function : load // purpose : @@ -68,13 +169,8 @@ bool RWGltf_TriangulationReader::load (const Handle(RWMesh_TriangulationSource)& const RWGltf_GltfPrimArrayData& aData = aDataIter.Value(); if (!aData.StreamData.IsNull()) { - Standard_ArrayStreamBuffer aStreamBuffer ((const char* )aData.StreamData->Data(), aData.StreamData->Size()); - std::istream aStream (&aStreamBuffer); - aStream.seekg ((std::streamoff )aData.StreamOffset, std::ios_base::beg); - if (!readBuffer (aSourceGltfMesh, theDestMesh, aStream, aData.Accessor, aData.Type)) - { - return false; - } + Message::SendWarning (TCollection_AsciiString("Buffer '") + aSourceGltfMesh->Id() + + "' contains stream data that cannot be loaded during deferred data loading."); continue; } else if (aData.StreamUri.IsEmpty()) @@ -83,14 +179,7 @@ bool RWGltf_TriangulationReader::load (const Handle(RWMesh_TriangulationSource)& return false; } - const Handle(OSD_FileSystem)& aFileSystem = !theFileSystem.IsNull() ? theFileSystem : OSD_FileSystem::DefaultFileSystem(); - opencascade::std::shared_ptr aSharedStream = aFileSystem->OpenIStream (aData.StreamUri, std::ios::in | std::ios::binary, aData.StreamOffset); - if (aSharedStream.get() == NULL) - { - reportError (TCollection_AsciiString ("Buffer '") + aSourceGltfMesh->Id() + "refers to invalid file '" + aData.StreamUri + "'."); - return false; - } - if (!readBuffer (aSourceGltfMesh, theDestMesh, *aSharedStream.get(), aData.Accessor, aData.Type)) + if (!readFileData (aSourceGltfMesh, aData, theDestMesh, theFileSystem)) { return false; } diff --git a/src/RWGltf/RWGltf_TriangulationReader.hxx b/src/RWGltf/RWGltf_TriangulationReader.hxx index ee10f68b12..821c08d350 100644 --- a/src/RWGltf/RWGltf_TriangulationReader.hxx +++ b/src/RWGltf/RWGltf_TriangulationReader.hxx @@ -21,6 +21,7 @@ #include class RWGltf_GltfLatePrimitiveArray; +class RWGltf_GltfPrimArrayData; //! RWMesh_TriangulationReader implementation creating Poly_Triangulation. class RWGltf_TriangulationReader : public RWMesh_TriangulationReader @@ -31,28 +32,66 @@ public: //! Empty constructor. Standard_EXPORT RWGltf_TriangulationReader(); + //! Loads only primitive arrays saved as stream buffer + //! (it is primarily glTF data encoded in base64 saved to temporary buffer during glTF file reading). + Standard_EXPORT bool LoadStreamData (const Handle(RWMesh_TriangulationSource)& theSourceMesh, + const Handle(Poly_Triangulation)& theDestMesh) const; + protected: //! Reports error. Standard_EXPORT virtual void reportError (const TCollection_AsciiString& theText) const; - //! Loads primitive array. + //! Loads only primitive arrays from file data. + //! @param theSourceMesh source triangulation + //! @param theDestMesh triangulation to be modified + //! @param theFileSystem shared file system to read from + //! Note: this method skips "stream data" that should be loaded by LoadStreamData() call. Standard_EXPORT virtual bool load (const Handle(RWMesh_TriangulationSource)& theSourceMesh, const Handle(Poly_Triangulation)& theDestMesh, const Handle(OSD_FileSystem)& theFileSystem) const Standard_OVERRIDE; //! Performs additional actions to finalize data loading. + //! @param theSourceMesh source triangulation + //! @param theDestMesh triangulation to be modified Standard_EXPORT virtual bool finalizeLoading (const Handle(RWMesh_TriangulationSource)& theSourceMesh, const Handle(Poly_Triangulation)& theDestMesh) const Standard_OVERRIDE; + //! Loads only primitive arrays saved as stream buffer + //! (it is primarily glTF data encoded in base64 saved to temporary buffer during glTF file reading). + //! @param theSourceMesh source triangulation + //! @param theDestMesh triangulation to be modified + //! @param theToResetStream if TRUE reset input stream data buffer after its loading. + Standard_EXPORT bool loadStreamData (const Handle(RWMesh_TriangulationSource)& theSourceMesh, + const Handle(Poly_Triangulation)& theDestMesh, + bool theToResetStream = true) const; + + //! Reads primitive array from stream data. + //! @param theSourceGltfMesh source glTF triangulation + //! @param theGltfData primitive array element (stream data should not be NULL) + //! @param theDestMesh triangulation to be modified + Standard_EXPORT bool readStreamData (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceGltfMesh, + const RWGltf_GltfPrimArrayData& theGltfData, + const Handle(Poly_Triangulation)& theDestMesh) const; + + //! Reads primitive array from file data. + //! @param theSourceGltfMesh source glTF triangulation + //! @param theGltfData primitive array element (Uri of file stream should not be empty) + //! @param theDestMesh triangulation to be modified + //! @param theFileSystem shared file system to read from + Standard_EXPORT bool readFileData (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceGltfMesh, + const RWGltf_GltfPrimArrayData& theGltfData, + const Handle(Poly_Triangulation)& theDestMesh, + const Handle(OSD_FileSystem)& theFileSystem) const; + //! Fills triangulation data and ignore non-triangulation primitives. - //! @param theSourceMesh source triangulation - //! @param theDestMesh triangulation to be modified - //! @param theStream input stream to read from - //! @param theAccessor buffer accessor - //! @param theType array type + //! @param theSourceGltfMesh source glTF triangulation + //! @param theDestMesh triangulation to be modified + //! @param theStream input stream to read from + //! @param theAccessor buffer accessor + //! @param theType array type //! @return FALSE on error - Standard_EXPORT virtual bool readBuffer (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceMesh, + Standard_EXPORT virtual bool readBuffer (const Handle(RWGltf_GltfLatePrimitiveArray)& theSourceGltfMesh, const Handle(Poly_Triangulation)& theDestMesh, std::istream& theStream, const RWGltf_GltfAccessor& theAccessor, diff --git a/tests/de_mesh/gltf_lateload/helmet b/tests/de_mesh/gltf_lateload/helmet new file mode 100644 index 0000000000..80f26be9b9 --- /dev/null +++ b/tests/de_mesh/gltf_lateload/helmet @@ -0,0 +1,12 @@ +puts "========" +puts "0032248: Visualization - load false deferred glTF data immediately" +puts "========" + +ReadGltf D [locate_data_file bug30691_DamagedHelmet.gltf] -skiplateloading 1 +XGetOneShape s D + +set info [trinfo s -lods] +if {![regexp {([0-9]+) +triangles.*Types: Poly_Triangulation \(1\)} $info dummy triangles_nb] + || $triangles_nb != 15452} { + puts "Fail: incorrect loading file with stream data" +}