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)

1 Like

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);
  }
}
2 Likes

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.

1 Like

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

What’s the strategy for that when binaryformatter is obsolete?