From 94453b0470f4db2e832090663530b2c0803c61ea Mon Sep 17 00:00:00 2001 From: codereader Date: Mon, 28 Sep 2020 19:26:04 +0200 Subject: [PATCH] #5346: Extract the IMapExporter interface, and add IMap::createMapExporter method to return an instance to enable the game connection plugin to export the scene. --- include/imap.h | 8 +++++ include/imapexporter.h | 33 ++++++++++++++++++++ plugins/dm.gameconnection/GameConnection.cpp | 11 +++++-- radiantcore/map/Map.cpp | 8 ++++- radiantcore/map/Map.h | 3 ++ radiantcore/map/MapResource.cpp | 7 +++-- radiantcore/map/algorithm/MapExporter.cpp | 24 +++++++------- radiantcore/map/algorithm/MapExporter.h | 12 ++++--- tools/msvc/dm.gameconnection.vcxproj | 8 ++--- tools/msvc/include.vcxproj | 1 + 10 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 include/imapexporter.h diff --git a/include/imap.h b/include/imap.h index 77518af9a1..6a0e730d11 100644 --- a/include/imap.h +++ b/include/imap.h @@ -2,6 +2,8 @@ #include "imodule.h" #include "inode.h" +#include "imapexporter.h" +#include "imapformat.h" #include "ikeyvaluestore.h" #include @@ -152,6 +154,12 @@ class IMap : // Caution: this is upposed to be called on startup, since it doesn't ask the user // whether to save the current map. Use the "NewMap" command for regular purposes. virtual void createNewMap() = 0; + + // Create a MapExporter instance which can be used to export a scene, + // including the necessary preparation, info-file handling, etc. + // This is mainly a service method for external code, like the gameconnection. + virtual map::IMapExporter::Ptr createMapExporter(map::IMapWriter& writer, + const scene::IMapRootNodePtr& root, std::ostream& mapStream) = 0; }; typedef std::shared_ptr IMapPtr; diff --git a/include/imapexporter.h b/include/imapexporter.h new file mode 100644 index 0000000000..bbf699f0b5 --- /dev/null +++ b/include/imapexporter.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "inode.h" +#include "imapformat.h" + +namespace map +{ + +/** + * Exporter class used to serialise a map to an output stream. + * Use the given exportMap() method to write a scene to the + * attached stream. For scene traversal the given functor + * is used to allow for custom filtering of the scene nodes. + * + * Use GlobalMapModule().createMapExporter() to acquire an + * instance of IMapExporter. + * + * Note: This is a scoped object by design, which will prepare + * the scene during construction and clean it up on destruction. + */ +class IMapExporter +{ +public: + using Ptr = std::shared_ptr; + + virtual ~IMapExporter() {} + + // Export the scene below the given root node using the given traversal function + virtual void exportMap(const scene::INodePtr& root, const GraphTraversalFunc& traverse) = 0; +}; + +} diff --git a/plugins/dm.gameconnection/GameConnection.cpp b/plugins/dm.gameconnection/GameConnection.cpp index 5b83ae34b8..882d645af5 100644 --- a/plugins/dm.gameconnection/GameConnection.cpp +++ b/plugins/dm.gameconnection/GameConnection.cpp @@ -11,6 +11,8 @@ #include "iuimanager.h" #include "ieventmanager.h" +#include "scene/Traverse.h" + #include #include @@ -478,8 +480,13 @@ std::string saveMapDiff(const DiffEntityStatuses& entityStatuses) } //write added/modified entities as usual - //TODO map::MapExporterPtr exporter(new map::MapExporter(writer, root, outStream, 0)); - //TODO exporter->exportMap(root, map::traverseSubset(subsetNodes)); + { + // Get a scoped exporter class + auto exporter = GlobalMapModule().createMapExporter(writer, root, outStream); + exporter->exportMap(root, scene::traverseSubset(subsetNodes)); + + // end the life of the exporter instance here to finish the scene + } return outStream.str(); } diff --git a/radiantcore/map/Map.cpp b/radiantcore/map/Map.cpp index 396285a591..d8225bd6fc 100644 --- a/radiantcore/map/Map.cpp +++ b/radiantcore/map/Map.cpp @@ -377,6 +377,12 @@ void Map::createNewMap() focusViews(Vector3(0,0,0), Vector3(0,0,0)); } +IMapExporter::Ptr Map::createMapExporter(IMapWriter& writer, + const scene::IMapRootNodePtr& root, std::ostream& mapStream) +{ + return std::make_shared(writer, root, mapStream, 0); +} + bool Map::import(const std::string& filename) { radiant::ScopedLongRunningOperation blocker(_("Importing...")); @@ -819,7 +825,7 @@ void Map::exportSelected(std::ostream& out, const MapFormatPtr& format) assert(format); // Create our main MapExporter walker for traversal - MapExporter exporter(*format, GlobalSceneGraph().root(), out); + MapExporter exporter(*format->getMapWriter(), GlobalSceneGraph().root(), out); // Pass the traverseSelected function and start writing selected nodes exporter.exportMap(GlobalSceneGraph().root(), scene::traverseSelected); diff --git a/radiantcore/map/Map.h b/radiantcore/map/Map.h index 7da1b85037..3e018bcc02 100644 --- a/radiantcore/map/Map.h +++ b/radiantcore/map/Map.h @@ -172,6 +172,9 @@ class Map : // greebo: Creates a new, empty map file. void createNewMap() override; + IMapExporter::Ptr createMapExporter(IMapWriter& writer, + const scene::IMapRootNodePtr& root, std::ostream& mapStream) override; + // Accessor methods for the worldspawn node void setWorldspawn(const scene::INodePtr& node); diff --git a/radiantcore/map/MapResource.cpp b/radiantcore/map/MapResource.cpp index 672feb9d81..a33cdf9bac 100644 --- a/radiantcore/map/MapResource.cpp +++ b/radiantcore/map/MapResource.cpp @@ -513,14 +513,15 @@ void MapResource::saveFile(const MapFormat& format, const scene::IMapRootNodePtr // and the destructor will clean it up afterwards. That way // we ensure a nice and tidy scene when exceptions are thrown. MapExporterPtr exporter; - + auto mapWriter = format.getMapWriter(); + if (format.allowInfoFileCreation()) { - exporter.reset(new MapExporter(format, root, outFileStream, *auxFileStream, counter.getCount())); + exporter.reset(new MapExporter(*mapWriter, root, outFileStream, *auxFileStream, counter.getCount())); } else { - exporter.reset(new MapExporter(format, root, outFileStream, counter.getCount())); // no aux stream + exporter.reset(new MapExporter(*mapWriter, root, outFileStream, counter.getCount())); // no aux stream } try diff --git a/radiantcore/map/algorithm/MapExporter.cpp b/radiantcore/map/algorithm/MapExporter.cpp index d586c95a9c..35122b99fe 100644 --- a/radiantcore/map/algorithm/MapExporter.cpp +++ b/radiantcore/map/algorithm/MapExporter.cpp @@ -25,8 +25,8 @@ namespace map const char* const RKEY_MAP_SAVE_STATUS_INTERLEAVE = "user/ui/map/saveStatusInterleave"; } -MapExporter::MapExporter(const MapFormat& format, const scene::IMapRootNodePtr& root, std::ostream& mapStream, std::size_t nodeCount) : - _writer(format.getMapWriter()), +MapExporter::MapExporter(IMapWriter& writer, const scene::IMapRootNodePtr& root, std::ostream& mapStream, std::size_t nodeCount) : + _writer(writer), _mapStream(mapStream), _root(root), _dialogEventLimiter(registry::getValue(RKEY_MAP_SAVE_STATUS_INTERLEAVE)), @@ -38,9 +38,9 @@ MapExporter::MapExporter(const MapFormat& format, const scene::IMapRootNodePtr& construct(); } -MapExporter::MapExporter(const MapFormat& format, const scene::IMapRootNodePtr& root, +MapExporter::MapExporter(IMapWriter& writer, const scene::IMapRootNodePtr& root, std::ostream& mapStream, std::ostream& auxStream, std::size_t nodeCount) : - _writer(format.getMapWriter()), + _writer(writer), _mapStream(mapStream), _infoFileExporter(new InfoFileExporter(auxStream)), _root(root), @@ -93,7 +93,7 @@ void MapExporter::exportMap(const scene::INodePtr& root, const GraphTraversalFun throw std::logic_error("Map node is not a scene::IMapRootNode"); } - _writer->beginWriteMap(mapRoot, _mapStream); + _writer.beginWriteMap(mapRoot, _mapStream); if (_infoFileExporter) { @@ -117,7 +117,7 @@ void MapExporter::exportMap(const scene::INodePtr& root, const GraphTraversalFun throw std::logic_error("Map node is not a scene::IMapRootNode"); } - _writer->endWriteMap(mapRoot, _mapStream); + _writer.endWriteMap(mapRoot, _mapStream); if (_infoFileExporter) { @@ -143,7 +143,7 @@ bool MapExporter::pre(const scene::INodePtr& node) // Progress dialog handling onNodeProgress(); - _writer->beginWriteEntity(entity, _mapStream); + _writer.beginWriteEntity(entity, _mapStream); if (_infoFileExporter) _infoFileExporter->visitEntity(node, _entityNum); @@ -157,7 +157,7 @@ bool MapExporter::pre(const scene::INodePtr& node) // Progress dialog handling onNodeProgress(); - _writer->beginWriteBrush(brush, _mapStream); + _writer.beginWriteBrush(brush, _mapStream); if (_infoFileExporter) _infoFileExporter->visitPrimitive(node, _entityNum, _primitiveNum); @@ -171,7 +171,7 @@ bool MapExporter::pre(const scene::INodePtr& node) // Progress dialog handling onNodeProgress(); - _writer->beginWritePatch(patch, _mapStream); + _writer.beginWritePatch(patch, _mapStream); if (_infoFileExporter) _infoFileExporter->visitPrimitive(node, _entityNum, _primitiveNum); @@ -194,7 +194,7 @@ void MapExporter::post(const scene::INodePtr& node) if (entity) { - _writer->endWriteEntity(entity, _mapStream); + _writer.endWriteEntity(entity, _mapStream); _entityNum++; return; @@ -204,7 +204,7 @@ void MapExporter::post(const scene::INodePtr& node) if (brush && brush->getIBrush().hasContributingFaces()) { - _writer->endWriteBrush(brush, _mapStream); + _writer.endWriteBrush(brush, _mapStream); _primitiveNum++; return; } @@ -213,7 +213,7 @@ void MapExporter::post(const scene::INodePtr& node) if (patch) { - _writer->endWritePatch(patch, _mapStream); + _writer.endWritePatch(patch, _mapStream); _primitiveNum++; return; } diff --git a/radiantcore/map/algorithm/MapExporter.h b/radiantcore/map/algorithm/MapExporter.h index 87e0e68921..aab827b921 100644 --- a/radiantcore/map/algorithm/MapExporter.h +++ b/radiantcore/map/algorithm/MapExporter.h @@ -1,6 +1,7 @@ #pragma once #include "inode.h" +#include "imapexporter.h" #include "imapformat.h" #include "imap.h" #include "igame.h" @@ -25,11 +26,12 @@ namespace map * the calling code needs to be able to handle that. */ class MapExporter : + public IMapExporter, public scene::NodeVisitor { private: - // The map format exporter - IMapWriterPtr _writer; + // For writing nodes to the stream + IMapWriter& _writer; // The stream we're writing to std::ostream& _mapStream; @@ -53,18 +55,18 @@ class MapExporter : public: // The constructor prepares the scene and the output stream - MapExporter(const MapFormat& format, const scene::IMapRootNodePtr& root, + MapExporter(IMapWriter& writer, const scene::IMapRootNodePtr& root, std::ostream& mapStream, std::size_t nodeCount = 0); // Additional constructor allowed to write to the auxiliary .darkradiant file - MapExporter(const MapFormat& format, const scene::IMapRootNodePtr& root, + MapExporter(IMapWriter& writer, const scene::IMapRootNodePtr& root, std::ostream& mapStream, std::ostream& auxStream, std::size_t nodeCount = 0); // Cleans up the scene on destruction ~MapExporter(); // Entry point for traversing the given root node using the given traversal function - void exportMap(const scene::INodePtr& root, const GraphTraversalFunc& traverse); + void exportMap(const scene::INodePtr& root, const GraphTraversalFunc& traverse) override; // NodeVisitor implementation, is called by the traversal func passed to MapResource bool pre(const scene::INodePtr& node) override; diff --git a/tools/msvc/dm.gameconnection.vcxproj b/tools/msvc/dm.gameconnection.vcxproj index 707d0b5ce1..e624daa2e2 100644 --- a/tools/msvc/dm.gameconnection.vcxproj +++ b/tools/msvc/dm.gameconnection.vcxproj @@ -89,14 +89,14 @@ true - wsock32.lib;%(AdditionalDependencies) + scenelib.lib;wsock32.lib;%(AdditionalDependencies) true - wsock32.lib;%(AdditionalDependencies) + scenelib.lib;wsock32.lib;%(AdditionalDependencies) @@ -104,7 +104,7 @@ true true - wsock32.lib;%(AdditionalDependencies) + scenelib.lib;wsock32.lib;%(AdditionalDependencies) @@ -112,7 +112,7 @@ true true - wsock32.lib;%(AdditionalDependencies) + scenelib.lib;wsock32.lib;%(AdditionalDependencies) diff --git a/tools/msvc/include.vcxproj b/tools/msvc/include.vcxproj index cf81a978ea..2b348f9c6a 100644 --- a/tools/msvc/include.vcxproj +++ b/tools/msvc/include.vcxproj @@ -139,6 +139,7 @@ +