diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d4a5589b19..c10321c702 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -222,6 +222,7 @@ target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} gz-rendering${GZ_RENDERING_VER}::core gz-transport${GZ_TRANSPORT_VER}::gz-transport${GZ_TRANSPORT_VER} gz-transport${GZ_TRANSPORT_VER}::parameters + gz-transport${GZ_TRANSPORT_VER}::log sdformat${SDF_VER}::sdformat${SDF_VER} protobuf::libprotobuf PRIVATE diff --git a/src/Server.cc b/src/Server.cc index 712f273467..3cf2276e53 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -24,6 +24,11 @@ #include #include #include +#include + +#include "gz/sim/components/World.hh" +#include "gz/sim/components/Name.hh" + #include #include #include @@ -58,6 +63,90 @@ struct DefaultWorld } }; + +namespace { +// A slow and ugly hack to restore Log playback +std::optional + WorldFromLog(std::string logPath) +{ + if (common::isFile(logPath)) + { + // TODO(arjo): Restore Zip-file + return std::nullopt; + } + + std::string dbPath = common::joinPaths(logPath, + "state.tlog"); + gzmsg << "Loading log file [" + dbPath + "]\n"; + if (!common::exists(dbPath)) + { + gzerr << "Log path invalid. File [" << dbPath << "] " + << "does not exist. Nothing to play.\n"; + return std::nullopt; + } + + auto log = std::make_unique(); + if (!log->Open(dbPath)) + { + gzerr << "Failed to open log file [" << dbPath << "]" << std::endl; + return std::nullopt; + } + + auto batch = log->QueryMessages(); + auto iter = batch.begin(); + + if (iter == batch.end()) + { + gzerr << "No messages found in log file [" << dbPath << "]" << std::endl; + return std::nullopt; + } + + EntityComponentManager tempEcm; + // Look for the first SerializedState message and use it to set the initial + // state of the world. Messages received before this are ignored. + for (; iter != batch.end(); ++iter) + { + auto msgType = iter->Type(); + if (msgType == "gz.msgs.SerializedState") + { + msgs::SerializedState msg; + msg.ParseFromString(iter->Data()); + tempEcm.SetState(msg); + break; + } + else if (msgType == "gz.msgs.SerializedStateMap") + { + msgs::SerializedStateMap msg; + msg.ParseFromString(iter->Data()); + tempEcm.SetState(msg); + break; + } + } + auto worldEntity = tempEcm.EntityByComponents(components::World()); + if (kNullEntity == worldEntity) + { + // Seems like levels logs can start without a world component + gzwarn << "Log file has no initial world component.\n"; + return DefaultWorld::World(); + } + + auto name = tempEcm.ComponentData(worldEntity); + if (!name.has_value()) + { + gzwarn << "Log file world has no name component.\n"; + return DefaultWorld::World(); + } + + std::stringstream worldTemplate; + worldTemplate << "" + << "" + << "" + << ""; + return worldTemplate.str(); +} + +} + ///////////////////////////////////////////////// Server::Server(const ServerConfig &_config) : dataPtr(new ServerPrivate) @@ -190,10 +279,20 @@ Server::Server(const ServerConfig &_config) case ServerConfig::SourceType::kNone: default: { - gzmsg << "Loading default world.\n"; - // Load an empty world. - /// \todo(nkoenig) Add a "AddWorld" function to sdf::Root. - errors = this->dataPtr->sdfRoot.LoadSdfString(DefaultWorld::World()); + if (_config.LogPlaybackPath() == "") + { + // Load an empty world. + /// \todo(nkoenig) Add a "AddWorld" function to sdf::Root. + errors = this->dataPtr->sdfRoot.LoadSdfString(DefaultWorld::World()); + } + else + { + auto world = WorldFromLog(_config.LogPlaybackPath()); + if (world.has_value()) + this->dataPtr->sdfRoot.LoadSdfString(world.value()); + else + return; + } break; } } diff --git a/test/integration/log_system.cc b/test/integration/log_system.cc index f867ca731a..1fa2ea6ab7 100644 --- a/test/integration/log_system.cc +++ b/test/integration/log_system.cc @@ -904,7 +904,7 @@ TEST_F(LogSystemTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(LogControl)) msgs::Boolean res; bool result{false}; unsigned int timeout = 1000; - std::string service{"/world/default/playback/control"}; + std::string service{"/world/shapes/playback/control"}; for (auto i : secs) { req.mutable_seek()->set_sec(i);