How to serialize a GH_Structure<IGH_Goo>?

Continuing the discussion from Model serialization:

Question as titled

image
I was trying to do this on my component. store is a variable pointing to a Dictionary, which as strings as keys and GH_Structure<IGH_Goo> as values. I need a ToBytes(). A little help please?

GH_Structure<T> has Write() and Read() methods, DataTree<T> does not.

And even then it depends on the type of data stored in the tree whether (de)serialisation will work.

This is beyond me. I wanted to create an equivalent to the data dam component, with a key pointer to each GH_Structure and it would save with grasshopper file. Is this possible?

Yes, although again it depends on the data inside the tree. If it’s some plug-in type without an obvious serialisation mechanism it won’t work.

I can maybe come up with an example tomorrow. Remind me again if I don’t.

no should be all RhinoCommon or GH_sometype

Here’s code for a component which ‘remembers’ the last data that went through it. So you can connect it to some input, then disconnect it and the data is still there. Even after a file Save/Open cycle.

using System;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;

namespace TestTreeSerialisation
{
  public sealed class PersistentDataComponent : GH_Component
  {
    public PersistentDataComponent()
    : base("Persistent Data", "PData", "Retain the last known data for as long as possible", "Data", "Test")
    { }

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
      pManager.AddGenericParameter("Data", "D", "Data to retain", GH_ParamAccess.tree);
      pManager[0].Optional = true;
    }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      pManager.AddGenericParameter("Data", "D", "Retained or passed data", GH_ParamAccess.tree);
    }

    public override Guid ComponentGuid
    {
      get { return new Guid("{20269257-D06D-4C0B-92FB-4704329A1112}"); }
    }

    protected override void SolveInstance(IGH_DataAccess access)
    {
      // If the input parameter has persistent data, or is connected to sources,
      // then assign the RetainedData from the input.
      // Otherwise use the retained data.

      var data = RetainedData;
      var input = Params.Input[0] as Param_GenericObject;
      if (input is null)
        throw new InvalidCastException("Input was supposed to be a Param_GenericObject.");

      if (input.SourceCount > 0 || !input.PersistentData.IsEmpty)
      {
        access.GetDataTree(0, out data);
        RetainedData = data.ShallowDuplicate();
      }

      if (data is null)
        data = new GH_Structure<IGH_Goo>();

      access.SetDataTree(0, data);
    }

    /// <summary>
    /// Gets or sets the retained data.
    /// </summary>
    private GH_Structure<IGH_Goo> RetainedData { get; set; }

    public override bool Write(GH_IWriter writer)
    {
      // Serialize the retained data if there's something there.
      var data = RetainedData;
      if (data != null && !data.IsEmpty)
        data.Write(writer.CreateChunk("RetainedData"));

      return base.Write(writer);
    }
    public override bool Read(GH_IReader reader)
    {
      RetainedData = null;

      // Deserialize the retained data if it exists.
      var chunk = reader.FindChunk("RetainedData");
      if (chunk != null)
      {
        var data = new GH_Structure<IGH_Goo>();
        data.Read(chunk);
        RetainedData = data;
      }

      return base.Read(reader);
    }
  }
}

retention test.gh (7.8 KB)

Thank you!
With your code pattern I improvised this

public class retainer:GH_Component
{
      protected Dictionary<string, GH_Structure<IGH_Goo>> storage = new Dictionary<string, GH_Structure<IGH_Goo>>();

      // .... some code that fills storage

      public override bool Write(GH_IWriter writer)
      {
          foreach (string k in storage.Keys)
              storage[k].Write(writer.CreateChunk(k));

          return base.Write(writer);
      }
      public override bool Read(GH_IReader reader)
      {
          foreach (var chunk in reader.Chunks)
          {
              var ghtree = new GH_Structure<IGH_Goo>();
              ghtree.Read(reader.FindChunk(chunk.Name));
              storage[chunk.Name] = ghtree.Duplicate();
          }
          return base.Read(reader);
      }
}

And of course it fails. IO error upon reopen of the gh file. Object set to null
What am I doing terribly wrong? By the way the test GH_Structure only has int in them.

This is a problem, the reader will contain a lot of chunks which aren’t yours. You need to name your own chunks in a way which allows you to find them all without trying ones that don’t belong to you.

I recommend naming them all the same and using an index to identify different ones.

I have to go do a recycling run now before they close, but I can tell you later how that might work.

1 Like

At your leisure of course. Thanks for support!
Btw if I bypass the null reference error with try/catch, I get a “chunk already exist” error in the Write override.

I have not tested this at all…

protected Dictionary<string, GH_Structure<IGH_Goo>> _storage = new Dictionary<string, GH_Structure<IGH_Goo>>();
private void WriteTest(GH_IWriter writer)
{
  int n = 0;
  foreach (var pair in _storage)
  {
    // Create a chunk with a specific name and an increasing index.
    var chunk = writer.CreateChunk("StoredData", n++);
    // Store the dictionary name as a string on the chunk.
    chunk.SetString("Key", pair.Key);

    // Store the dictionary value in a sub-chunk.
    var treeChunk = chunk.CreateChunk("Tree");
    pair.Value.Write(treeChunk);
  }
}
private void ReadTest(GH_IReader reader)
{
  _storage.Clear();

  // Try and read as many "StoredData" chunks as possible.
  // This approach demands that all these chunks are stored under increasing indices.
  for (int i = 0; i < int.MaxValue; i++)
  {
    var chunk = reader.FindChunk("StoredData", i);
    // Stop looking once we've run out.
    if (chunk is null) break;

    var key = chunk.GetString("Key");
    var treeChunk = chunk.FindChunk("Tree");

    var tree = new GH_Structure<IGH_Goo>();
    tree.Read(treeChunk);

    _storage.Add(key, tree);
  }
}

I have indeed tested that…
YES it works. Thank you!
Would this be worthy of incorporating in the standard set of components that ships with RH/GH? Fussy people like me may script a generation of things on a “seed-implicit” Random, and this would provide a way of storing options deliberately.

Yes, GH2 needs way more features that actually deal with data in the abstract. That will include caching and deserializing and all that stuff.