Storing Data Between Commands, Document User Data VS Shared Instances

Previously, Steve explained that for any plugin data (strings, geometries, and guids) I want to maintain between commands, a good practice is to store them in shared instances within my plugin class.

In addition to storing between commands, I plan on storing between sessions, which I will do with document user data. So that gets me wondering if I should just use this latter method all the time (for between commands too), and forget about using shared instances in my plugin class. In essence, the user data becomes my variables I use from one command to the next.

I like the consistency of this approach, but before going that way I thought I would consult you all as this would be a major change and a lot of work. The one issue I can think of already is that while guids can be converted to strings for storing in the document user data, I’m not sure how to do this with geometries. Is there a way? If need be, I could always add geometries needing tracking to the document, and then fetch their geometry via trycast of their guid.

Beyond the implementation though, I am curious as to which approach is considered better as far as computational efficiency, robustness, good practice etc. (shared instance in plugin class VS document user data). Any input is appreciated.

Thanks,
Sam

Hi Sam,

The details of your question are a little vague. But I’ll give you my opinion on what I think is the best practice.

First, only your Rhino plug-in object can read and write document user data. So it makes some sense for the plug-in object to be the owner of that. By owner, I mean it is the object which holds on to the data. And since it is easy for any command or user interface gizmo to get a hold of the one and only plug-in instance, there should be no problem in accessing the data.

This sample plug-in gives a good example of the above model.

https://github.com/dalefugier/SampleCsDocumentUserData

In the example, the plug-in object contains a string table which is accessible to any command.

As far as what you can serialize, a BinaryArchiveWriter can write geometry as well as simple data types like ints and strings. So, depending on what you are doing you might be able to serialize geometry as user data (too).

The critical piece of serializing user data is versioning. Make sure you always write version information. This way, if you need to change your data, your plug-in can account for multiple versions of your data.

Hope this helps.

– Dale

Hi Dale,

I came across another thread you were providing support in:

The solution you presented there, serializing entire classes, appeals to me and I would like to give it a shot. I’ve combined this code with some from the link you provided into a VB.net project (attached).

There is a class called shoe, and there are two shared instances of it within the plugin class, one for the right and one for the left. The EnterDataCommand sets, as an example, the x, y, and z properties while the UseDataCommand creates a point at these coordinates. I would like to run EnterDataCommand, then save the 3dm, close rhino, reopen the 3dm and be able to run UseDataCommand.

I imagine there are many places I’m going wrong… but first problem I’m suspecting is that the subs in the “Document user data overrides” region aren’t being called, and I’m not sure why.

Hope you can point me in the right direction.
Thank you kindly,
Sam

SerializingTest.zip (55.8 KB)

In general, your code looks file. But you want to take great care when reading and writing user data of any kind. Thus, your plug-ins WriteDocument() and ReadDocument() member should be more robust.

I’ve rewritten your sample just a bit…

That worked great, very pleased! Some refinement questions:

  1. In regards to the UnsetValue, this is just a place holder so that things aren’t ever nothing, right? If so, what is the problem with things being nothing?

  2. I was noticing the plugin class instances don’t get cleared when a new document is created. Should I do this, and if so, what’s the best way?

  3. I’m going to have multiple classes (Shoe, Foot, Orthotic, Last, etc.). I’m thinking it might be a bit repetitive to have the read and write functions in each of these classes. Maybe I could put the read and write functions in the plugin class somehow. I got stumped trying to figure out how to do this… Is it even possible?

  4. For the ‘create’ subroutine, I’m going to have a lot of properties, so its a bit of a pain to always write me.property = src.property for each. I was hoping I could just write me = src, but that’s a no go. Any suggestions?

  5. As far as versioning, perhaps I’m getting a bit ahead of myself, but I was thinking of attaching the version separately with a user string, and then having multiple versions of my classes, and then in any given session, using the class version that matches up with the file version. Maybe you have some suggestions for me now… though it’s a question I could ask later when I have my head better wrapped around it all.

This approach you’ve provided is going to save me a lot of time!
Thanks,
Sam

Keeping in mind that this is just a sample…

1.) This is just my way of making sure the property is initialized and knowing that the property is not valid.

2.) I’ve updated the project by adding some event handlers to clear the properties when a new document is opened or when a document is closed.

3.) Robust, high-quality file I/O is very critical. So no short cuts here. I’m even a little hesitant to show this “automatic” way of doing class I/O because it only works if the class is never going to change. As soon as you add additional variables, it becomes more challenging to support older version of your data that are already out in field.

4.) Understanding how .NET assignment operators work is also critical. With classes, doing A=B does not make a copy. It makes A point to (or reference). So probably no shortcuts here either.

5.) With the method demonstrated, you may have to provide multiple version of your classes. If you were to write and read each data property individually, you could just modify your class and your Read and Write routines, taking version numbers into account.

As an alternative to using .NET serialization, I’ve added a new “SampleVbVector” class to the project that demonstrates a more typical way of serializing class data. The advantage of this method is that it is easy to modify the class. Just add any new members and then tune up the Read and Write members, taking the updates into account.

Thanks Dale, I’m working on incorporating all this into my plugin. I’m making progress at the moment, but I bet I will have some more related questions over the next while.

In the mean time, I had a really quick question, but I’ll ask that in a new thread so as to keep this one focused.

Thanks,
Sam

Hi Dale,

I finally found the source of an error I was getting when trying to write: when one of my properties in the class for which instances are saved is a RhinoObject, writing to the archive doesn’t seem to work:
Public Property TestRhinoObject As Rhino.DocObjects.RhinoObject

I can work around this, but just curious what the issue might be.

One other quick question: I’m finding it a bit crowded to always write out:
SampleVbCustomPointPlugIn.Instance.Point0.ToString()

I thought I could at least make the first bit unnecessary with:
Imports SampleVbCustomPoint.SampleVbCustomPoint.SampleVbCustomPointPlugIn

Curiously, that worked for some of my classes but not others, no clue why… Any suggestions on how to shorten things?

Thanks,
Sam

Only Rhino will write a RhinoObject. For user data, you will have to write the geometry and the attributes yourself (separately).

Why would you convert a point to a string? How to you plan on reading it back in (and converting it back to a point? Why not just write/read the point using BinaryArchiveWriter.WritePoint3d and BinaryArchiveReader.ReadPoint3d?

Hi Dale,

I was referring to the below line of code in your project and asking if there was some way to shorten it, to not have to write plugin.instance every time…

RhinoApp.WriteLine(“Point 0 = {0}”, SampleVbCustomPointPlugIn.Instance.Point0.ToString())

Anyway, it’s not that important. You’ve answered all my questions and I’m quite pleased with the reading and writing functionality.

Thank you,
Sam

Hi Dale,

I have another question about reading and writing as you demonstrated in the SampleVBPoint class in your SampleVBCustomPoint project.

The write function writes an entire class. For what I’m doing, I like this approach rather than writing all the properties individually. However, what I just realized, is that it may also write any functions and subs in the class too. This isn’t necessary as I only wish to save the properties, and might be harmful as someone could then reverse engineer the functions and subs from the written 3DM file. However, I am obfuscating the RHP file, so maybe this gives the same protection to the subs and functions written to the 3DM file, or perhaps the 3DM file already offers its own protection?

As an alternative, I was thinking I could move these subs and functions to other classes (probably rhinocommon command classes), but this would require a lot of work: I have to add in prefixes for all of the properties, which is a lot and I can’t figure out how to automate that…

I’ve got the feeling I could be missing something here… any thoughts, suggestions?

Thanks,
Sam

Serialization can be defined as the process of storing the state of an object instance to a storage medium. During this process, the public and private fields of the object and the name of the class, including the assembly containing the class, is converted to a stream of bytes. Functions, subroutines, and source code are not serialized.

https://msdn.microsoft.com/en-us/library/ms973893.aspx

Excellent, saves me a big headache!
Thanks,
Sam