Shader that gets data from an object: Refresh



  • Hello,

    I'm writing a channel shader. It has a LINK box where the user links an object from the scene. The object is also a plugin of mine. The shader gets some data from the object and renders it. It already works fine with editor and PV renders.

    But here is a question:

    When I change attributes of the object, I want the shader to update automatically. Currently, the editor/viewport representation of the shader only changes when I force it to update by eg. changing a shader attribute. Is there a standard way to either notify all instances of my shader if my plugin object changes; or can a shader somehow observe the linked object to detect changes?

    Thanks in advance!

    Cheers from Berlin,
    Frank



  • Hi,

    have tried the "standard hacky way " of evaluating the dirty state of the object node in question in NodeData::Message of your shader node?

    I also am always completely at a loss again every time I encounter such scenario and what message to choose / piggy back on, because of the rather lacklustre documentation of the message system of Cinema. You would have to look at the message feed and choose an appropriate message id or just blanked in the evaluation if no message id alone seems sufficient. This solution is obviously not perfect, as it still might result in noticeable delays until the change of data is reflected in the viewport.

    Cinema probably also needs at some point a modern event subscription / delegate logic system, because it is one of the most common problems here on the forum. The overhead of such system is not so bad on modern machines anymore that it would justify completely ignoring such feature.

    Cheers,
    zipit



  • I have had to do this a few times in the past. What I do is store a BaseLink to all the Shaders that are used on my Object. I register and deregister them in the SetDParameter methods of the Shader itself. IE the shader registerers itself with the objects it is added to. And when the shader is destroyed it unregisters itself from the object it is applied to. Then when the object needs the shader to update it goes through its list of shaders that are registered to it and sets its dirty flag. SetDirty(DIRTYFLAGS::ALL); This should work fine for you since you are also storing a link to the object in your shader itself, which is also required so it knows what to register and deregister itself to.



  • Hi @fwilleke80 unfortually I just come to confirm there is no real way or at least nothing that comes with Cinema 4D.

    Your best bet is to implement as Kent suggested a kind of observable pattern.

    Cheers,
    Maxime.



  • Thank you very much, I'll try that!

    Cheers,
    Frank

    P.S.: There might be follow-up questions to this, but I'll mark this as SOLVED for now.



  • It works like a charm!
    Thank you again!

    I was surprised at how little code was required.

    Sharing is caring. In case anyone needs it, here's the code:

    #include "ge_prepass.h"
    #include "c4d_general.h"
    #include "c4d_baselinkarray.h"
    #include "c4d_basedocument.h"
    
    ///
    /// \brief Registers observers and sends messages to them.
    ///
    class Observable
    {
    public:
    	///
    	/// \brief Subscribes a new observer.
    	///
    	/// \param[in] observer Pointer to an AtomGoal
    	/// \param[in] doc The document that owns the AtomGoal
    	///
    	/// \return A maxon error object if anything went wrong, otherwise maxon::OK
    	///
    	maxon::Result<void> Subscribe(C4DAtomGoal *observer, BaseDocument *doc);
    		
    	///
    	/// \brief Unsubscribes an observer
    	///
    	/// \param[in] observer Pointer to an AtomGoal that has previously been subscribed
    	/// \param[in] doc The document that owns the AtomGoal
    	///
    	void Unsubscribe(C4DAtomGoal *observer, BaseDocument *doc);
    		
    	///
    	/// \brief Sends a messages to all subscribed observers
    	///
    	/// \param[in] type Message type
    	/// \param[in] doc The document that owns the subscribed observers
    	/// \param[in] data Optional message data
    	///
    	void Message(Int32 type, BaseDocument *doc, void *data = nullptr) const;
    
    private:
    	BaseLinkArray _observers;
    };
    
    maxon::Result<void> Observable::Subscribe(C4DAtomGoal *observer, BaseDocument *doc)
    {
    	if (!observer)
    		return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Observer must not be nullptr!"_s);
    
    	// Check if this observer is already registered
    	const Int32 observerIndex = _observers.Find(observer, doc);
    	if (observerIndex != NOTOK)
    		return maxon::OK;
    
    	// Register new observer
    	if (!_observers.Append(observer))
    	{
    		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Failed to add observer to the list!"_s);
    	}
    
    	return maxon::OK;
    }
    
    void Observable::Unsubscribe(C4DAtomGoal *observer, BaseDocument *doc)
    {
    	if (observer && doc)
    	{
    		const Int32 observerIndex = _observers.Find(observer, doc);
    		if (observerIndex != NOTOK)
    		{
    			_observers.Remove(observerIndex);
    		}
    	}
    }
    
    void Observable::Message(Int32 type, BaseDocument *doc, void *data) const
    {
    	for (Int32 i = 0; i < _observers.GetCount(); ++i)
    	{
    		C4DAtomGoal *atom = _observers.GetIndex(i, doc);
    		if (atom)
    		{
    			atom->Message(type, data);
    		}
    	}
    }