Rebuilding a plugin without breaking saved scenes?



  • On 12/12/2015 at 21:59, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R17 
    Platform:    Mac  ;  
    Language(s) :     C++  ;

    ---------
    I've got a plugin here that I've been working on rewriting. The new version of the plugin is entirely different from the old, the whole GUI was overhauled and in the process I've changed a lot of the description values.

    As-is, none of my older scenes open up with the new plugin. Most of the settings don't come in at all, and the occasional shader link that does is in the wrong place. I'm assuming this is because the description IDs of the new GUI do not line up with the old one.

    Is there some way to have my plugin gracefully migrate itself from the old version to the new? The SDK documents mention using NodeData::Read()/NodeData::Write()/NodeData::CopyTo(), but at the same time the footer for Read() mentions this:

    If all values are stored in the node's container, you do not have to deal with the level.

    All the settings for my plugin were stored in the data container via GetDataInstance(). Does this mean that Read/Write/CopyTo don't apply to me? What else should I use if that's the case?

    -CMPX



  • On 13/12/2015 at 04:59, xxxxxxxx wrote:

    Well, changing the description IDs is the one thing you should never do if you want the new version to load old scenes. The description IDs are the connection between the GUI, the internationalized text files, and the values in a BaseContainer, so if you move IDs around, all the saved values in your old files get jumbled.

    For example, if you originally saved the value MY_PLUGIN_POSITION_X as ID 1001, and now it's 1002 and the ID 1001 is re-used for MY_PLUGIN_POSITION_Y, then all your old X values appear as Y, and the current X gets the defaults. (Even worse if the data types of the saved IDs do not match the data types that the new version expects.)

    So, once you have released a plugin ("released" meaning: you or other people have actual scenes saved with that plugin in it), you must not ever assign a new ID to an existing description. You can "retire" a description by simply not using it any more, but the ID of that description must not be reused (keep it in your .h definition with a comment). You can also add a new description which then will be assigned the default value for all old scenes, but it must have a new ID that has not been used in old versions of the plugin.

    The advantage of the BaseContainer is that it is semantically agnostic; it just saves a set of values with certain numeric IDs and certain data types which are known to C4D. That means as long as you use a BaseContainer to store values, you cannot break the file loading process. You also do not need to bother about reading, writing, or copying a BaseContainer since the container itself knows how to.

    (A different case would be if you use custom data types; since C4D does not know how these are structured, you need to implement ReadData, WriteData, CopyData and Compare functions. In this case you use the disklevel to determine whether a file was written by an old version of the plugin, and implement the appropriate read functionality for each disklevel.)

    However, "semantically agnostic" also means that the responsibility of the interpretation of the contained values is left to the higher levels - the plugin itself, or the standard C4D functions that it calls (like showing a description). And that in turn means you cannot just redefine an ID to have a different semantics.

    An update to a plugin that is already used needs careful planning to keep as much data from old versions as possible (which means keeping the IDs for these values intact).

    If there is no meaningful reuse for these values, it might be prudent to simply treat the new plugin as something completely different, get a new PluginID for it, and perform the migration from the old plugin manually.

    If you think that your old scenes are worth saving, you may try the following (but be aware that I never did something like this, so it's speculative...) :
    1. Overwrite the Read() function of your Node. This should work since the method is virtual.
    2. In that overwrite, first call the parent class's Read(), so all the actual disk access is done.
    3. Then try to recognize whether the data in the just-read BaseContainer is from the old or the new version (for example, by reading a value that is only present in the new version, or maybe you have defined a Disklevel for the container already...)
    4. If the data is from the old version, create a new BaseContainer and perform a mapping from the old values with their old IDs to the new values with their new IDs. Store all the new values with the new IDs in the new container.
    If the data is from the new version, you don't need to do anything.
    5. Replace the container in the NodeData with the new container which is now adapted to the new IDs and values.

    ... 6. Tell me if that worked, so I don't have to test it myself ;-)



  • On 13/12/2015 at 05:39, xxxxxxxx wrote:

    Howdy,

    Here's how I've done it before:

    In the plugin's Register function, I set the level parameter to 1 for the new version.

    Then in the Read function I store the level in the plugin's BaseContainer:

    Bool MyPlugin::Read(GeListNode* node, HyperFile* hf, LONG level)
    {
    	BaseTag *tag  = (BaseTag* )node;
    	BaseContainer *tData = tag->GetDataInstance();
    	tData->SetLong(MYPLUGIN_DISK_LEVEL,level);
    	
    	return true;
    }
    

    Then in the Message function, I poll for the message MSG_MULTI_DOCUMENTIMPORTED:

    case MSG_MULTI_DOCUMENTIMPORTED:
    {
    	if(doc && tData->GetLong(MYPLUGIN_DISK_LEVEL) < 1)
    	{
    		ConvertOldData(doc,tData);
    	}
    	break;
    }
    

    The "ConvertOldData" function is where I convert the old containers to the new ones.

    In the Message function make sure you get the document at the top:

    BaseDocument *doc = node->GetDocument();
    

    Adios,
    Cactus Dan



  • On 13/12/2015 at 05:41, xxxxxxxx wrote:

    Whoa.

    Thank you both the in-depth reply. This was work done on an unreleased plugin (mostly in preparation to release it for sale), so the only scenes that are screwed up from the UI overhaul are my own. It sounds like I'll probably land up just fixing those rather then trying to muck around with NodeData::Read() and parsing that data manually to try and figure out what has been moved around.

    Serves me right for getting lazy with the first iteration of this thing, I guess.

    -CMPX



  • On 13/12/2015 at 05:47, xxxxxxxx wrote:

    Howdy,

    Oh, if the plugin hasn't been released yet, you can simply poll for MSG_MULTI_DOCUMENTIMPORTED and write temporary code there to update your old files, then delete the code. 😉

    Adios,
    Cactus Dan


Log in to reply