BaseLinkArray looses link? (was: Refreshing a nested BaseShader)



  • Hello,

    this is related to: https://plugincafe.maxon.net/topic/12823/shader-that-gets-data-from-an-object-refresh/6

    My ShaderData uses data from an ObjectData to generate its output. It requests the data from the object during InitRender(). Using the pattern shown in the above linked thread, the ObjectData sets the Shader dirty (DIRTYFLAGS::DATA) and also sends an MSG_UPDATE to it, once the ObjectData has finished computing its data. So, after, every change to the ObjectData's attributes, the ShaderData should be dirty and is told to update.

    This works flawlessly, if the shader sits directly e.g. in a material's Color channel (even without the MSG_UPDATE). However, if I put it into another shader, e.g. a Colorizer, the shader's result is not updated until I change any of the shader's attributes. What can do I have to do with the shader to make sure it is updated, and the material preview is re-rendered, even if it's located somewhere deep down in other shaders in the material?

    EDIT: I found out what the reason is! Just don't know the solution.

    As shown in the above linked thread, I am using a BaseLinkArray to keep a list of observers. Initially, this works fine. The ObjectData holds an Observable object, and the ShaderData nicely subscribes to it. Calling const Int32 observerCount = _observers.GetRealCount(doc); on the BaseLinkArray returns 1. Setting the ShaderData dirty from within the ObjectData works.

    But after I put the shader into a Colorizer, _observers.GetRealCount(doc) returns 0, while _observers.GetCount() still returns 1. Therefore, using _observers.GetIndex() to get the link to the shader from the array returns nullptr. It seems the BaseLink in the array is broken after putting the linked shader into another one.

    Why? Well, probably because the BaseShader that now sits inside the Colorizer is not the same object as the one that was there before, right? But shouldn't that be updated automatically by Cinema, when the shader is placed inside the Colorizer? Isn't that what BaseLinks are meant for?

    Thanks & greetings,
    Frank



  • My first idea would be, to catch a specific message that is maybe sent to the shader when it is being put into the Colorizer.

    However, the only message that comes in when this happens, is 1001071 (MSG_MULTI_RENDERNOTIFICATION). I guess I could use it to update the BaseLink in the observable. But it seems that message comes in very often, simply when clicking through the material editor. Does not sound like a good idea.



  • hi,

    one question / suggestion :

    is there any difference if you change the order of creating your shader ?
    for example
    add your shader -> add colorizer (that will move your shader inside the colorizer)
    add colorizer-> go inside and add your shader.

    If there's any change, that could mean that your shader is "copied" and not only moved.
    I would check if the CopyTo function is used in the process. That would allow you to update the object's array.

    I would need a bit of time to recreate an example and see if i can find a solution.
    (you can probably test it before me)

    Cheers,
    Manuel



  • Hi Manuel,

    thanks for the hint! This does in deed sound promising.

    And yes, the shader is copied when put into the Colorizer. As far as I remember, almost nothing in Cinema is ever moved, it's all copied.
    I added CopyTo() to my shader class, and implemented it like this:

    Bool MyShader::CopyTo(NodeData *dest, GeListNode *snode, GeListNode *dnode, COPYFLAGS flags, AliasTrans *trn)
    {
    	iferr_scope_handler
    	{
    		GePrint(err.GetMessage());
    		return false;
    	};
    
    	// Get document of destination node
    	// Note: When CopyTo() is called because the shader is moved into another shader (e.g. Colorizer), this is always nullptr.
    	BaseDocument *destDoc = dnode->GetDocument();
    
    	// Get container of source node
    	const BaseContainer &dataRef = static_cast<BaseShader*>(snode)->GetDataInstanceRef();
    
    	// Get linked object from source node's container
    	BaseObject *linkedObject = dataRef.GetObjectLink(XTERRAINOPERATORSHADER_OPERATORLINK, snode->GetDocument());
    
    	// If we have a linked object, and if the destination node has a document
    	if (linkedObject && destDoc)
    	{
    		// Get the node's ObjectData
    		MyObjectData *linkedNodeData = linkedObject->GetNodeData<MyObjectData>();
    		if (! linkedNodeData)
    			return false;
    
    		// Get ref to the Observable object
    		MyObservable &observable = linkedNodeData->GetObservableRef();
    
    		// Subscribe to shader to observable
    		observable.Subscribe(dnode, destDoc) iferr_return;
    	}
    
    	return SUPER::CopyTo(dest, snode, dnode, flags, trn);
    }
    

    It changes nothing. Still doesn't work. _observers.GetIndex() still returns nullptr, even though _observers.GetCount() returns 1.

    What I noticed:

    • MyShader::CopyTo() is called numerous times, even if I just navigate through the material manager. When I put my shader into a Colorizer, CopyTo() is called 10 times!
    • When CopyTo()is called because I move the shader into a Colorizer, dnode->GetDocument() returns nullptr. I tried using snode->GetDocument() instead. Now, after creating my shader, then moving it into a Colorizer, and then changing the Colorizer's gradient once, I have 25(!) subscribers in the observable. And all the BaseLinks are broken!! Not a single one can be retrieved with GetIndex().

    I still ask myself why I even have to do all this. Isn't the purpose of BaseLinks to have stable links to nodes even if they're moved and change pointer address? All in all, I'm starting to wonder if a hidden InExcludeList in MyObject's description wouldn't be easier. Incredibly clumsy, yes, but it might just work...

    Cheers,
    Frank



  • hi,

    About the hidden include list, yes it should work. Note that even if you store the baselink inside the basecontainer of your object, they should be updated by an aliastrans.
    That would be a bit better than an hidden in/exclude list.

    Cheers,
    Manuel



  • Aha, that's interesting!

    So, if I got this right, the problem is that my BaseLinkArray is hidden inside my NodeData where it can't be found by Cinema, hence can't be updated by the AliasTrans?

    If it was just one BaseLink it would be easy. But I have an arbitrary number of BaseLinks in a BaseLinkArray. Can that somehow be stored in the BaseContainer? Or is there any other way to make my BaseLinkArray known to Cinema, so it can be updated? Or a way to update it myself?

    In the SDK docs, it says about AliasTrans::Translate(): "Translates the links in all objects that the translator has come across.". Can I make it "come across" my BaseLinkArray? :D

    @kbar: In the other thread you mentioned that you also implemented an observer pattern. Did you run into this problem, too?

    Thanks & greetings,
    Frank



  • hi,
    when you Init your aliastrans, it trying to find all it can everywhere. So every BaseContainer are scanned.
    That also include the document's BaseContainer.

    As you can't say aliastrans where to look, you have to place your data where it can find them.

    (but the hidden in/exclude should work)



  • Thank you :)

    Cheers,
    Frank



  • Just a little follow-up on this:

    It would be very much appreciated if there was like a Message() coming in, or a callback, or some other way to simply present any BaseLink members of the NodeData to an AliasTrans.

    Cheers,
    Frank