From 7d203443c3450eedd73c5f709e5f0bf510f228bd Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Wed, 9 Sep 2020 09:50:09 +0200
Subject: [PATCH] ENH: refactor surface writer collated time management (#1600)

- abstracted out from ensight surface writer for potential reuse by
  other surface writers.
---
 src/surfMesh/Make/files                       |   1 +
 .../writers/caching/surfaceWriterCaching.C    | 275 +++++++++++++
 .../writers/caching/surfaceWriterCaching.H    | 163 ++++++++
 .../writers/ensight/ensightSurfaceWriter.C    |  14 +-
 .../writers/ensight/ensightSurfaceWriter.H    |  24 +-
 .../ensight/ensightSurfaceWriterCollated.C    | 361 +++++-------------
 .../ensight/ensightSurfaceWriterUncollated.C  |  10 +-
 7 files changed, 560 insertions(+), 288 deletions(-)
 create mode 100644 src/surfMesh/writers/caching/surfaceWriterCaching.C
 create mode 100644 src/surfMesh/writers/caching/surfaceWriterCaching.H

diff --git a/src/surfMesh/Make/files b/src/surfMesh/Make/files
index 0fe30852b3b..7c68022eb8b 100644
--- a/src/surfMesh/Make/files
+++ b/src/surfMesh/Make/files
@@ -62,6 +62,7 @@ triSurface/patches/surfacePatch.C
 writers = writers
 
 $(writers)/surfaceWriter.C
+$(writers)/caching/surfaceWriterCaching.C
 $(writers)/boundaryData/boundaryDataSurfaceWriter.C
 $(writers)/ensight/ensightSurfaceWriter.C
 $(writers)/foam/foamSurfaceWriter.C
diff --git a/src/surfMesh/writers/caching/surfaceWriterCaching.C b/src/surfMesh/writers/caching/surfaceWriterCaching.C
new file mode 100644
index 00000000000..b4f5f156401
--- /dev/null
+++ b/src/surfMesh/writers/caching/surfaceWriterCaching.C
@@ -0,0 +1,275 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "surfaceWriterCaching.H"
+#include "ListOps.H"
+#include "Fstream.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Compare time values with tolerance
+static const equalOp<scalar> equalTimes(ROOTSMALL);
+
+// Use ListOps findLower (with tolerance), to find the location of the next
+// time-related index.
+// The returned index is always 0 or larger (no negative values).
+static label findTimeIndex(const UList<scalar>& list, const scalar val)
+{
+    label idx =
+        findLower
+        (
+            list,
+            val,
+            0,
+            [](const scalar a, const scalar b)
+            {
+                return (a < b) && (Foam::mag(b - a) > ROOTSMALL);
+            }
+        );
+
+    if (idx < 0 || !equalTimes(list[idx], val))
+    {
+        ++idx;
+    }
+
+    return idx;
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::surfaceWriters::writerCaching::writerCaching(const word& cacheFileName)
+:
+    dictName_(cacheFileName)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::dictionary& Foam::surfaceWriters::writerCaching::fieldsDict() const
+{
+    const dictionary* dictptr = cache_.findDict("fields", keyType::LITERAL);
+
+    if (!dictptr)
+    {
+        dictptr = &dictionary::null;
+    }
+
+    return *dictptr;
+}
+
+
+Foam::dictionary& Foam::surfaceWriters::writerCaching::fieldDict
+(
+    const word& fieldName
+)
+{
+    return
+        cache_
+            .subDictOrAdd("fields", keyType::LITERAL)
+            .subDictOrAdd(fieldName, keyType::LITERAL);
+}
+
+
+bool Foam::surfaceWriters::writerCaching::remove(const word& fieldName)
+{
+    dictionary* dictptr = cache_.findDict("fields", keyType::LITERAL);
+
+    if (dictptr)
+    {
+        return dictptr->remove(fieldName);
+    }
+
+    return false;
+}
+
+
+void Foam::surfaceWriters::writerCaching::clear()
+{
+    times_.clear();
+    geoms_.clear();
+    cache_.clear();
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::surfaceWriters::writerCaching::readPreviousTimes
+(
+    const fileName& dictFile,
+    const scalar timeValue
+)
+{
+    // In 1906 and earlier, the fieldsDict contained "meshes" and "times"
+    // entries, each with their own time values.
+    // This makes it more difficult to define the exact correspondence
+    // between geometry intervals and times.
+    //
+    // Now track the used geometry intervals as a bitSet.
+
+
+    // Only called from master
+    label timeIndex = 0;
+    cache_.clear();
+
+    IFstream is(dictFile);
+
+    if (is.good() && cache_.read(is))
+    {
+        geoms_.clear();
+
+        cache_.readIfPresent("times", times_);
+        timeIndex = findTimeIndex(times_, timeValue);
+
+        labelList geomIndices;
+        scalarList meshTimes;
+
+        if (cache_.readIfPresent("geometry", geomIndices))
+        {
+            // Convert indices to bitSet entries
+            geoms_.set(geomIndices);
+        }
+        else if (cache_.readIfPresent("meshes", meshTimes))
+        {
+            WarningInFunction
+                << nl
+                << "Setting geometry timeset information from time values"
+                << " (cache from an older OpenFOAM version)." << nl
+                << "This may not be fully reliable." << nl
+                << nl;
+
+            for (const scalar meshTime : meshTimes)
+            {
+                const label geomIndex = findTimeIndex(times_, meshTime);
+                geoms_.set(geomIndex);
+            }
+        }
+
+        // Make length consistent with time information.
+        // We read/write the indices instead of simply dumping the bitSet.
+        // This makes the contents more human readable.
+        geoms_.resize(times_.size());
+    }
+
+    return timeIndex;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::surfaceWriters::writerCaching::update
+(
+    const fileName& baseDir,
+    const scalar timeValue,
+    const bool geomChanged,
+    const word& fieldName,
+    const word& fieldType,
+    const word& varName
+)
+{
+    const fileName dictFile(baseDir/dictName_);
+
+    bool stateChanged = false;
+
+    const label timeIndex =
+    (
+        times_.empty()
+      ? readPreviousTimes(dictFile, timeValue)
+      : findTimeIndex(times_, timeValue)
+    );
+
+
+    // Update stored times list and geometry index
+
+    if (timeIndex < geoms_.size()-1)
+    {
+        // Clear old content when shrinking
+        geoms_.unset(timeIndex);
+    }
+
+    // Extend or truncate list
+    geoms_.resize(timeIndex+1);
+    times_.resize(timeIndex+1, VGREAT);
+
+    if (!equalTimes(times_[timeIndex], timeValue))
+    {
+        stateChanged = true;
+        times_[timeIndex] = timeValue;
+    }
+
+    if (geomChanged)
+    {
+        stateChanged = true;
+        geoms_.set(timeIndex);
+    }
+
+
+    // Update time/geometry information in dictionary
+    cache_.set("times", times_);
+    cache_.set("geometry", geoms_.sortedToc());
+
+    // Debugging, or if needed for older versions:
+    //// cache_.set
+    //// (
+    ////     "meshes",
+    ////     IndirectList<scalar>(times_, geoms_.sortedToc())
+    //// );
+
+    // Add field information to dictionary
+    dictionary& dict = fieldDict(fieldName);
+
+    if (dict.empty())
+    {
+        stateChanged = true;
+
+        dict.set("type", fieldType);
+        if (!varName.empty() && varName != fieldName)
+        {
+            // Use variable name, if it differs from fieldName
+            dict.set("name", varName);
+        }
+    }
+
+    if (stateChanged)
+    {
+        OFstream os(dictFile);
+        os << "// State file for surface writer output" << nl << nl;
+        cache_.write(os, false);
+
+        os << nl << "// End" << nl;
+    }
+
+    return stateChanged;
+}
+
+
+// ************************************************************************* //
diff --git a/src/surfMesh/writers/caching/surfaceWriterCaching.H b/src/surfMesh/writers/caching/surfaceWriterCaching.H
new file mode 100644
index 00000000000..3ad22142b00
--- /dev/null
+++ b/src/surfMesh/writers/caching/surfaceWriterCaching.H
@@ -0,0 +1,163 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::surfaceWriters::writerCaching
+
+Description
+    Information for surface writers with collated times.
+
+    The class maintains an internal list of the known times
+    as well as a file-cached version with the field information.
+    The information is used for restarts.
+
+SourceFiles
+    surfaceWriterCaching.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef surfaceWriters_writerCaching_H
+#define surfaceWriters_writerCaching_H
+
+#include "bitSet.H"
+#include "dictionary.H"
+#include "scalarList.H"
+#include "DynamicList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace surfaceWriters
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class writerCaching Declaration
+\*---------------------------------------------------------------------------*/
+
+class writerCaching
+{
+    // Private Data
+
+        //- Cache dictionary file name
+        word dictName_;
+
+        //- The output times
+        DynamicList<scalar> times_;
+
+        //- Indices in times_ when geometry (mesh) has been written
+        bitSet geoms_;
+
+        //- Cached information for geometry, times, fields
+        dictionary cache_;
+
+
+    // Private Member Functions
+
+        //- Read time information from dictFileName.
+        //  Returns timeIndex corresponding to timeValue
+        label readPreviousTimes
+        (
+            const fileName& dictFile,
+            const scalar timeValue
+        );
+
+        //- Get or create a sub-dictionary for named field
+        dictionary& fieldDict(const word& fieldName);
+
+        //- Remove named field
+        bool remove(const word& fieldName);
+
+
+public:
+
+    // Constructors
+
+        //- Construct with specified cache name
+        explicit writerCaching(const word& cacheFileName);
+
+
+    //- Destructor
+    virtual ~writerCaching() = default;
+
+
+    // Member Functions
+
+        //- The output times for fields
+        const scalarList& times() const
+        {
+            return times_;
+        }
+
+        //- Indices in times() when geometry (mesh) has been written
+        const bitSet& geometries() const
+        {
+            return geoms_;
+        }
+
+        //- The most current time index
+        label latestTimeIndex() const
+        {
+            return max(0, times_.size()-1);
+        }
+
+        //- The most current geometry index
+        label latestGeomIndex() const
+        {
+            return max(0, geoms_.find_last());
+        }
+
+        //- Get or create the 'fields' information dictionary.
+        const dictionary& fieldsDict() const;
+
+        //- Clear all values
+        void clear();
+
+        //- Update time/geometry information and file cache.
+        //- This routine should only be called from the master process
+        //  \return True if there is a state change, which is either a
+        //      geometry change or a new time interval
+        bool update
+        (
+            const fileName& baseDir,    //!< Directory containing the cache file
+            const scalar timeValue,     //!< The current time value
+            const bool geomChanged,     //!< Monitored geometry changed
+            const word& fieldName,      //!< Name of field
+            const word& fieldType,      //!< Type of field
+            const word& varName = word::null //!< Alternative field name
+        );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace surfaceWriters
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriter.C b/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
index 63da8297517..e90c9d5f481 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
@@ -56,7 +56,7 @@ void Foam::surfaceWriters::ensightWriter::printTimeset
 (
     OSstream& os,
     const label ts,
-    const scalar& timeValue
+    const scalar timeValue
 )
 {
     os
@@ -176,7 +176,8 @@ Foam::surfaceWriters::ensightWriter::ensightWriter()
 :
     surfaceWriter(),
     writeFormat_(IOstream::ASCII),
-    collateTimes_(true)
+    collateTimes_(true),
+    caching_("fieldsDict")  // Historic name
 {}
 
 
@@ -190,7 +191,8 @@ Foam::surfaceWriters::ensightWriter::ensightWriter
     (
         IOstreamOption::formatEnum("format", options, IOstream::ASCII)
     ),
-    collateTimes_(options.getOrDefault("collateTimes", true))
+    collateTimes_(options.getOrDefault("collateTimes", true)),
+    caching_("fieldsDict")  // Historic name
 {}
 
 
@@ -227,15 +229,13 @@ Foam::surfaceWriters::ensightWriter::ensightWriter
 
 void Foam::surfaceWriters::ensightWriter::close()
 {
-    times_.clear();
-    meshes_.clear();
-    cache_.clear();
+    caching_.clear();
     surfaceWriter::close();
 }
 
 
 // Note that ensight does supports geometry in a separate file,
-// but setting this true leaves mesh files in the wrong places
+// but setting this true leaves geometry files in the wrong places
 // (when there are fields).
 //
 // Make this false to let the field writers take back control
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriter.H b/src/surfMesh/writers/ensight/ensightSurfaceWriter.H
index 9b95072db2a..4c4c97855e0 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriter.H
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriter.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2015-2019 OpenCFD Ltd.
+    Copyright (C) 2015-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -61,8 +61,7 @@ SourceFiles
 #define ensightSurfaceWriter_H
 
 #include "surfaceWriter.H"
-#include "bitSet.H"
-#include "DynamicList.H"
+#include "surfaceWriterCaching.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -87,27 +86,12 @@ class ensightWriter
         //- Collate times (default: true)
         bool collateTimes_;
 
-        //- The collated output times
-        DynamicList<scalar> times_;
-
-        //- Indices in times_ when geometry (mesh) has been written (collated)
-        bitSet meshes_;
-
         //- Cached information for times, geometry, fields (collated)
-        dictionary cache_;
+        writerCaching caching_;
 
 
     // Private Member Functions
 
-        //- Read time information from baseDir / dictName.
-        //  Returns timeIndex corresponding to timeValue
-        label readPreviousTimes
-        (
-            const fileName& baseDir,
-            const word& dictName,
-            const scalar& timeValue
-        );
-
         //- The geometry can be any of the following:
         //
         // 0: constant/static
@@ -120,7 +104,7 @@ class ensightWriter
         (
             OSstream& os,
             const label ts,
-            const scalar& timeValue
+            const scalar timeValue
         );
 
         //- Print time-set for ensight case file, with N times and 0-based
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C b/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
index 4dc8fdeeda3..010195a4e75 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
@@ -26,122 +26,20 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// Compare time values with tolerance
-static const equalOp<scalar> equalTimes(ROOTSMALL);
-
-// Use ListOps findLower (with tolerance), to find the location of the next
-// time-related index.
-// The returned index is always 0 or larger (no negative values).
-static label findTimeIndex(const UList<scalar>& list, const scalar val)
-{
-    label idx =
-        findLower
-        (
-            list,
-            val,
-            0,
-            [](const scalar a, const scalar b)
-            {
-                return (a < b) && (Foam::mag(b - a) > ROOTSMALL);
-            }
-        );
-
-    if (idx < 0 || !equalTimes(list[idx], val))
-    {
-        ++idx;
-    }
-
-    return idx;
-}
-
-} // End namespace Foam
-
-
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-Foam::label Foam::surfaceWriters::ensightWriter::readPreviousTimes
-(
-    const fileName& baseDir,
-    const word& dictName,
-    const scalar& timeValue
-)
-{
-    // In 1906 and earlier, the fieldsDict contained "meshes" and "times"
-    // entries, each with their own time values.
-    // This makes it more difficult to define the exact correspondence
-    // between geometry intervals and times.
-    //
-    // We now instead track used geometry intervals as a bitSet.
-
-
-    // Only called from master
-
-    label timeIndex = 0;
-
-    labelList geomIndices;
-    scalarList meshTimes;
-
-    cache_.clear();
-
-    const fileName dictFile(baseDir/dictName);
-
-    if (isFile(dictFile))
-    {
-        IFstream is(dictFile);
-
-        if (is.good() && cache_.read(is))
-        {
-            meshes_.clear();
-
-            cache_.readIfPresent("times", times_);
-            timeIndex = findTimeIndex(times_, timeValue);
-
-            if (cache_.readIfPresent("geometry", geomIndices))
-            {
-                // Convert indices to bitSet entries
-                meshes_.set(geomIndices);
-            }
-            else if (cache_.readIfPresent("meshes", meshTimes))
-            {
-                WarningInFunction
-                    << nl
-                    << "Setting geometry timeset information from time values"
-                    << " (fieldsDict from an older OpenFOAM version)." << nl
-                    << "This may not be fully reliable." << nl
-                    << nl;
-
-                for (const scalar& meshTime : meshTimes)
-                {
-                    const label meshIndex = findTimeIndex(times_, meshTime);
-                    meshes_.set(meshIndex);
-                }
-            }
-
-            // Make length consistent with time information.
-            // We read/write the indices instead of simply dumping the bitSet.
-            // This makes the contents more human readable.
-            meshes_.resize(times_.size());
-        }
-    }
-
-    return timeIndex;
-}
-
-
 int Foam::surfaceWriters::ensightWriter::geometryTimeset() const
 {
-    if (meshes_.count() <= 1)
+    const scalarList& times = caching_.times();
+    const bitSet& geoms = caching_.geometries();
+
+    if (geoms.count() <= 1)
     {
         // Static
         return 0;
     }
 
-    if (meshes_.size() == times_.size() && meshes_.all())
+    if (geoms.size() == times.size() && geoms.all())
     {
         // Geometry changing is the same as fields changing
         return 1;
@@ -158,7 +56,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated()
 {
     // Collated?
     // ========
-    // Geometry:  rootdir/surfaceName/surfaceName.case
+    // CaseFile:  rootdir/surfaceName/surfaceName.case
     // Geometry:  rootdir/surfaceName/surfaceName.mesh
 
     wroteGeom_ = true;
@@ -173,6 +71,9 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
     const Field<Type>& localValues
 )
 {
+    // Geometry changed since last output? Capture now before any merging.
+    const bool geomChanged = (!upToDate_);
+
     checkOpen();
 
     const ensight::FileName surfName(outputPath_.name());
@@ -181,7 +82,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
 
     // Collated
     // ========
-    // Geometry:  rootdir/surfaceName/surfaceName.case
+    // CaseFile:  rootdir/surfaceName/surfaceName.case
     // Geometry:  rootdir/surfaceName/data/<index>/geometry
     // Field:     rootdir/surfaceName/data/<index>/field
 
@@ -210,11 +111,8 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
     }
 
 
-    // Mesh changed since last output? Do before any merging.
-    const bool meshChanged = (!upToDate_);
-
 
-    // geometry merge() implicit
+    // Implicit geometry merge()
     tmp<Field<Type>> tfield = mergeField(localValues);
 
     const meshedSurf& surf = surface();
@@ -226,42 +124,21 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
             mkDir(outputFile.path());
         }
 
-        bool stateChanged = meshChanged;
-
-        const label timeIndex =
-        (
-            times_.empty()
-          ? readPreviousTimes(baseDir, "fieldsDict", timeValue)
-          : findTimeIndex(times_, timeValue)
-        );
-
-
-        // Update stored times list and mesh index
-
-        if (timeIndex < meshes_.size()-1)
-        {
-            // Clear old content when shrinking
-            meshes_.unset(timeIndex);
-        }
-
-        // Extend or truncate list
-        meshes_.resize(timeIndex+1);
-        times_.resize(timeIndex+1, VGREAT);
-
-        if (meshChanged)
-        {
-            meshes_.set(timeIndex);
-        }
-
-        if (!equalTimes(times_[timeIndex], timeValue))
-        {
-            stateChanged = true;
-            times_[timeIndex] = timeValue;
-        }
+        const bool stateChanged =
+            caching_.update
+            (
+                baseDir,
+                timeValue,
+                geomChanged,
+                fieldName,
+                ensightPTraits<Type>::typeName,
+                varName
+            );
 
 
-        // The most current geometry index
-        const label geomIndex(max(0, meshes_.find_last()));
+        // The most current time and geometry indices
+        const label timeIndex = caching_.latestTimeIndex();
+        const label geomIndex = caching_.latestGeomIndex();
 
 
         // This will be used for the name of a static geometry,
@@ -273,126 +150,97 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
 
 
         // Do case file
+        if (stateChanged)
         {
-            // Add time information to dictionary
-            cache_.set("geometry", meshes_.sortedToc());
-            cache_.set("times", times_);
-
-            // Debugging, or if needed for older versions:
-            //// cache_.set
-            //// (
-            ////     "meshes",
-            ////     IndirectList<scalar>(times_, meshes_.sortedToc())
-            //// );
-
-            // Add field information to dictionary
-            dictionary& fieldsDict = cache_.subDictOrAdd("fields");
-            dictionary& fieldDict = fieldsDict.subDictOrAdd(fieldName);
-
-            if (fieldDict.empty())
+            OFstream osCase(outputFile, IOstream::ASCII);
+
+            // Format options
+            osCase.setf(ios_base::left);
+            osCase.setf(ios_base::scientific, ios_base::floatfield);
+            osCase.precision(5);
+
+            if (verbose_)
             {
-                fieldDict.set("type", ensightPTraits<Type>::typeName);
-                fieldDict.set("name", varName); // ensight variable name
-                stateChanged = true;
+                Info<< "Writing case file to " << osCase.name() << endl;
             }
 
+            // The geometry can be any of the following:
+            // 0: constant/static
+            // 1: moving, with the same frequency as the data
+            // 2: moving, with different frequency as the data
 
-            if (stateChanged)
-            {
-                if (verbose_)
-                {
-                    Info<< "Writing state file to fieldsDict" << endl;
-                }
-                {
-                    OFstream os(baseDir/"fieldsDict");
-                    os << "// Summary of Ensight fields, times" << nl << nl;
-                    cache_.write(os, false);
-                }
-
-                OFstream osCase(outputFile, IOstream::ASCII);
-
-                // Format options
-                osCase.setf(ios_base::left);
-                osCase.setf(ios_base::scientific, ios_base::floatfield);
-                osCase.precision(5);
-
-                if (verbose_)
-                {
-                    Info<< "Writing case file to " << osCase.name() << endl;
-                }
-
-                // The geometry can be any of the following:
-                // 0: constant/static
-                // 1: moving, with the same frequency as the data
-                // 2: moving, with different frequency as the data
-
-                const label tsGeom = geometryTimeset();
+            const label tsGeom = geometryTimeset();
+
+            osCase
+                << "FORMAT" << nl
+                << "type: ensight gold" << nl
+                << nl
+                << "GEOMETRY" << nl;
 
-                osCase
-                    << "FORMAT" << nl
-                    << "type: ensight gold" << nl
-                    << nl
-                    << "GEOMETRY" << nl;
-
-
-                if (tsGeom)
-                {
-                    // moving
-                    osCase
-                        << "model:  " << tsGeom << "   " // time-set (1|2)
-                        << mask << geometryName.name() << nl;
-                }
-                else
-                {
-                    // steady
-                    osCase
-                        << "model:  "
-                        << geometryName.c_str() << nl;
-                }
 
+            if (tsGeom)
+            {
+                // moving
+                osCase
+                    << "model:  " << tsGeom << "   " // time-set (1|2)
+                    << mask << geometryName.name() << nl;
+            }
+            else
+            {
+                // steady
                 osCase
-                    << nl
-                    << "VARIABLE" << nl;
+                    << "model:  "
+                    << geometryName.c_str() << nl;
+            }
 
+            osCase
+                << nl
+                << "VARIABLE" << nl;
 
-                for (const entry& dEntry : fieldsDict)
-                {
-                    const dictionary& subDict = dEntry.dict();
 
-                    const word fieldType(subDict.get<word>("type"));
-                    const word varName
+            for (const entry& dEntry : caching_.fieldsDict())
+            {
+                const dictionary& subDict = dEntry.dict();
+
+                const word varType(subDict.get<word>("type"));
+                const word varName
+                (
+                    subDict.getOrDefault<word>
                     (
-                        subDict.getOrDefault<word>
-                        (
-                            "name",
-                            dEntry.keyword() // fieldName as fallback
-                        )
-                    );
-
-                    osCase
-                        << fieldType
-                        <<
-                        (
-                            this->isPointData()
-                          ? " per node:    1  "  // time-set 1
-                          : " per element: 1  "  // time-set 1
-                        )
-                        << setw(15) << varName << ' '
-                        << mask << varName << nl;
-                }
+                        "name",
+                        dEntry.keyword() // fieldName as fallback
+                    )
+                );
 
                 osCase
-                    << nl
-                    << "TIME" << nl;
+                    << varType
+                    <<
+                    (
+                        this->isPointData()
+                      ? " per node:    1  "  // time-set 1
+                      : " per element: 1  "  // time-set 1
+                    )
+                    << setw(15) << varName << ' '
+                    << mask << varName << nl;
+            }
 
-                printTimeset(osCase, 1, times_);
-                if (tsGeom == 2)
-                {
-                    printTimeset(osCase, 2, times_, meshes_);
-                }
+            osCase
+                << nl
+                << "TIME" << nl;
 
-                osCase << "# end" << nl;
+            printTimeset(osCase, 1, caching_.times());
+            if (tsGeom == 2)
+            {
+                printTimeset
+                (
+                    osCase,
+                    tsGeom,
+                    caching_.times(),
+                    caching_.geometries()
+                );
             }
+
+            osCase << "# end" << nl;
         }
 
 
@@ -403,27 +251,28 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
         mkDir(dataDir);
 
 
-        const fileName meshFile(baseDir/geometryName);
+        const fileName geomFile(baseDir/geometryName);
 
         // Ensight Geometry
         ensightOutputSurface part
         (
             surf.points(),
             surf.faces(),
-            meshFile.name()
+            geomFile.name()
         );
 
-        if (!exists(meshFile))
+        if (!exists(geomFile))
         {
             if (verbose_)
             {
-                Info<< "Writing mesh file to " << meshFile.name() << endl;
+                Info<< "Writing geometry to " << geomFile.name() << endl;
             }
+
             // Two-argument form for path-name to avoid validating base-dir
             ensightGeoFile osGeom
             (
-                meshFile.path(),
-                meshFile.name(),
+                geomFile.path(),
+                geomFile.name(),
                 writeFormat_
             );
             part.write(osGeom); // serial
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C b/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
index 6a129ca0230..23ac92e38c4 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
@@ -37,7 +37,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated()
 
     // Uncollated
     // ==========
-    // Geometry:  rootdir/<TIME>/surfaceName.case
+    // CaseFile:  rootdir/<TIME>/surfaceName.case
     // Geometry:  rootdir/<TIME>/surfaceName.00000000.mesh
 
     fileName outputDir;
@@ -85,7 +85,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated()
             << nl
             << "TIME" << nl;
 
-        printTimeset(osCase, 1, 0.0);
+        printTimeset(osCase, 1, scalar(0));
 
         ensightOutputSurface part
         (
@@ -116,9 +116,9 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated
 
     // Uncollated
     // ==========
-    // geometry:  rootdir/time/<field>/surfaceName.case
-    // geometry:  rootdir/time/<field>/surfaceName.<index>.mesh
-    // field:     rootdir/time/<field>/surfaceName.<index>.<field>
+    // CaseFile:  rootdir/time/<field>/surfaceName.case
+    // Geometry:  rootdir/time/<field>/surfaceName.<index>.mesh
+    // Field:     rootdir/time/<field>/surfaceName.<index>.<field>
 
     // Variable name as sub-directory for results. Eg,
     // - VAR1/SURF1.case
-- 
GitLab