SpecialEventAdd unpredictable timing to GUI::Command



  • Hi everybody,

    I have a class GUI : public GeDialog class and I have implemented the function bool GUI::Command(Int32 id, const BaseContainer &msg)

    I can send a message to that function like this: SpecialEventAdd(CUSTOMEVENT::UPDATE_UI_DIALOG, 3);

    My issue is that my main thread is running a for loop and when it finishes it calls SpecialEventAdd and then starts another for loop, but my message isn't in caught in bool GUI::Command until after the for second for loop finishes. Is there anyway to make the main thread wait for the SpecialEventAdd message is caught?



  • Shouldn't you catch core messages by implementing GeDialog::CoreMessage()?

    SpecialEventAdd() adds a message to an event queue, which is processed at some point. So I don't think you can force that to process the message synchronously.

    If you somehow want to update the UI, there is GeUpdateUI(). But I don't know if that is synchronous or not.



  • Your totally right!

    I actually do catch it in Bool GUI::CoreMessage(Int32 id, const BaseContainer &bc).
    Basically yes i want to force it synchronously =(

    If i wanted to try GeUpdateUI() i think i would have the same issue because inside where I catch the message then I call SetString(DLG_DYNAMIC_RENDER_TEXT, "text"_s); so the message needs to still come before GeUpdateUI() i think.

    Should I update the UI with a different function then call GeUpdateUI().

    ApplicationOutput(); has the same issue. Its like the way to get synchronous output is writing to a file but then I cant see it in realtime.



  • Hi,

    I think it would be best if you would provide some (dummy) code to illustrate what you are doing, because there multiple things that remain unclear (at least to me).

    1. Normally you should not have to worry about updating a GeDialog at all, unless it contains a custom gadget that has not been implemented properly. Modifying the values / objects of your gadgets should update the dialog automatically.
    2. The phrasing "in my main thread" is somewhat ambiguous. Are you referring to the main thread of Cinema 4D with that or the thread of your application in which you perform the more expensive computations? If the latter is the case, all GUI calls from this thread will be ignored by Cinema 4D.
    3. Although this has not been revealed by Maxon officiallly, it seems very likely that also Cinema 4D is executing a main event loop. You marking something to be redrawn will never be executed immediately, but only when you are back in the redraw state of that loop. Everything else would be wildly inefficient - think of hundreds of elements all invoking their own redraw. If your app makes this processes sluggish, you have to execute the computationally expensive parts of your code in a thread other than Cinema's main thread, i.e. make it non-blocking for the main-loop.
    4. The way you (and @PluginStudent) are using the word "synchronously" confuses me a bit. Messages in Cinema 4D are being processed synchronously to the context they are being called in (which is usually the main thread). Synchronously means "in a blocking manner" or "with non-overlapping bars" for a process diagram. So I assume you both actually mean asynchronously, but invoking a message asynchronously to the context you are currently in won't change much, because GUI operations will still be executed synchronously with the main thread of Cinema 4D. Which in turn will come full circle when the original context/thread you spawned the message from was the main thread of Cinema 4D.

    Cheers,
    zipit



  • Hi Zipit - sorry it took time to get back to this. Here is some sample code that I hope shows what i am trying to do. in theZoomInOut function i want the SpecialEventAdd(); message to be caught by GUI::CoreMessage before the second for loop in in ZoomInOut starts.

    In GUI::CoreMessage I catch the SpecialEventAdd(); message and call SetString(DLG_DYNAMIC_RENDER_TEXT, "test changed text"_s); which does change the AddStaticText dialog on my panel, but it doesnt update until after the second for loop.

    1. Do you see something I have implemented wrong? When I am not doing lots of draw calls its does update without having to do anything.
    2. I mean the main application thread. I cant implement a second thread because as I understand only the main application thread can make draw calls.
    3. I agree about your theory of a main a loop running and the draw call is executed at intervals. All the draw calls do make the app sluggish. My intent is to time how long the draw calls take so sluggish may be expected and have to draw in the main loop so it will be blocking but that is okay for my use.
    4. I think we were expecting that when you call SpecialEventAdd(); that would be caught by GUI::CoreMessage which calls AddStaticText() and then would continue after SpecialEventAdd(); where more draw calls are. But since I think your right about a loop in C4D itself the GUI::CoreMessage part of the loop isnt called when there are draw calls in the "queue". Maybe looking for a way to force it synchronously
    /////main.cpp///// 
    Bool GUI::CoreMessage(Int32 id, const BaseContainer &bc)
    {
        
    	switch (id)
    	{
    	case CUSTOMEVENT::UPDATE_UI_DIALOG:
    	{
    
    		SetString(DLG_DYNAMIC_RENDER_TEXT, "test changed text"_s);
    	}
    	}
    }
    
    bool GUI::CreateLayout()
    {
    	
    	AddStaticText(DLG_DYNAMIC_RENDER_TEXT, BFH_LEFT, 250, 12, "initial text "_s, BORDER_NONE);
    
    
    	
    	return true;
    }
    
    
    ////testclass.cpp////
    void TestClass::ZoomInOut(int steps)
    {
    	for (int i = 0; i < steps; i++)
    	{
    		CallCommand(14063);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_REDUCTION | DRAWFLAGS::STATICBREAK);
    	}
    	
           //this message isnt caught until the for loop below it is completed, need it to be synchronous 
    	SpecialEventAdd(CUSTOMEVENT::UPDATE_UI_DIALOG);
    
    	for (int i = 0; i < steps; i++)
    	{
    		CallCommand(14064);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_REDUCTION | DRAWFLAGS::STATICBREAK);
    	}
    }
    

    Thank you for your help!



  • Hi,

    due to the flag DRAWFLAGS::NO_THREAD, your multiple viewport redraws are executed synchronously and make the execution of TestClass::ZoomInOut rather lengthy. This in turn blocks the redraw of your dialog, because that will only happen once the program left the scope of ZoomInOut (or more precisely the scope of the GeDialog method that called ZoomInOut).

    Despite that:

    • I would not use CallCommand in a plugin. At least not in such a brute force manner. Modifying the camera attached to the active viewport seems more elegant.
    • Depending on the value of steps: The execution of possibly hundreds or thousands of viewport redraws seems a bit excessive for a single command.

    Cheers,
    zipit



  • hi,

    Sorry, i don't understand what's the goal of your code ? Maybe it's just a design issu. That could surely help us to understand what you are trying to do.

    Your function Bool GUI::CoreMessage(Int32 id, const BaseContainer &bc) in main.cpp ?

    More important, who is calling ZoomInOut and from where ?

    This question need some lights.

    For example, if ZoomInOut is called in your tread, you can make it loop until the special event have done its job. If it's called in the main thread it's not a good way to go.

    Cheers,
    Manuel



  • @zipit

    Hi Zipit - I do want the DRAWFLAGS::NO_THREAD because when I time the draw calls having other threads would give me inconsistent timing over multiple tests. It sounds like the way C4D works this is all expected and because i am forcing so many draw calls the GeDialog doesnt get updated till after.

    Your right about CallCommand - just being lazy to zoom in and out instead of changing the editor camera position.
    My use case dictates lots of draw calls, it is in the hundreds to thousands.

    Thank you!



  • @m_magalhaes

    Hi m_magalhaes - The goal is to time how long it takes to do all the draw calls inside ZoomInOut.
    Yes Bool GUI::CoreMessage(Int32 id, const BaseContainer &bc) in in main.cpp

    I added to the code to show there is a button in GUI::CreateLayout() that calls GUI::Command(Int32 id, const BaseContainer &msg) which then calls ZoomInOut

    I have to call ZoomInOut from the main C4D thread because DrawViews can only be called by the main thread. I think that is how i understand the documentation.

    /////main.cpp///// 
    
    TestClass testClassInstance = TestClass();
    
    Bool GUI::CoreMessage(Int32 id, const BaseContainer &bc)
    {
        
    	switch (id)
    	{
    	case CUSTOMEVENT::UPDATE_UI_DIALOG:
    	{
    
    		SetString(DLG_DYNAMIC_RENDER_TEXT, "test changed text"_s);
    	}
    	}
    }
    
    bool GUI::CreateLayout()
    {
    	
    	AddStaticText(DLG_DYNAMIC_RENDER_TEXT, BFH_LEFT, 250, 12, "initial text "_s, BORDER_NONE);
    	AddButton(RUN_TESTS, BFH_LEFT, 0, 0, "Run Test"_s);
    
    	
    	return true;
    }
    
    bool GUI::Command(Int32 id, const BaseContainer &msg)
    {
    	switch (id)
    	{
    	case RUN_TESTS:
    	{
    		testClassInstance.ZoomInOut();
    	}
    	}
    
    }
    
    
    ////testclass.cpp////
    void TestClass::ZoomInOut(int steps)
    {
    	for (int i = 0; i < steps; i++)
    	{
    		CallCommand(14063);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_REDUCTION | DRAWFLAGS::STATICBREAK);
    	}
    	
           //this message isnt caught until the for loop below it is completed, need it to be synchronous 
    	SpecialEventAdd(CUSTOMEVENT::UPDATE_UI_DIALOG);
    
    	for (int i = 0; i < steps; i++)
    	{
    		CallCommand(14064);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_REDUCTION | DRAWFLAGS::STATICBREAK);
    	}
    }
    


  • hi,

    I would expect the gui to be with your class, the main only registering your CommandData but as long as you find yourself confortable, that's the same.

    What i could suggest is once you hit the button "start" you can use SetTimer using 1 as parameter.

    You can override the function Timer or catch the message BFM_TIMER_MESSAGE inside your Message function.

    you can call

    DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW|DRAWFLAGS::NO_REDUCTION|DRAWFLAGS::NO_THREAD|DRAWFLAGS::NO_ANIMATION|DRAWFLAGS::PRIVATE_NO_WAIT_GL_FINISHED|DRAWFLAGS::NO_HIGHLIGHT_PLANE);
    

    Cheers,
    Manuel



  • hi,

    without feedback, i'll set this thread to solved tomorrow.

    Cheers,
    Manuel


Log in to reply