SpecialEventAdd unpredictable timing to GUI::Command
-
Hi everybody,
I have a
class GUI : public GeDialogclass and I have implemented the functionbool 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
forloop and when it finishes it callsSpecialEventAddand then starts anotherforloop, but my message isn't in caught inbool GUI::Commanduntil after the for secondforloop finishes. Is there anyway to make the main thread wait for theSpecialEventAddmessage 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 callSetString(DLG_DYNAMIC_RENDER_TEXT, "text"_s);so the message needs to still come beforeGeUpdateUI()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).
- Normally you should not have to worry about updating a
GeDialogat 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. - 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.
- 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.
- 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 - Normally you should not have to worry about updating a
-
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 the
ZoomInOutfunction i want theSpecialEventAdd();message to be caught byGUI::CoreMessagebefore the secondforloop in inZoomInOutstarts.In
GUI::CoreMessageI catch theSpecialEventAdd();message and callSetString(DLG_DYNAMIC_RENDER_TEXT, "test changed text"_s);which does change theAddStaticTextdialog on my panel, but it doesnt update until after the secondforloop.- 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.
- 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.
- 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.
- I think we were expecting that when you call
SpecialEventAdd();that would be caught byGUI::CoreMessagewhich callsAddStaticText()and then would continue afterSpecialEventAdd();where more draw calls are. But since I think your right about a loop in C4D itself theGUI::CoreMessagepart 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 ofTestClass::ZoomInOutrather lengthy. This in turn blocks the redraw of your dialog, because that will only happen once the program left the scope ofZoomInOut(or more precisely the scope of theGeDialogmethod that calledZoomInOut).Despite that:
- I would not use
CallCommandin 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 - I would not use
-
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
ZoomInOutand from where ?This question need some lights.
For example, if
ZoomInOutis 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_THREADbecause 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.
YesBool GUI::CoreMessage(Int32 id, const BaseContainer &bc)in inmain.cppI added to the code to show there is a button in
GUI::CreateLayout()that callsGUI::Command(Int32 id, const BaseContainer &msg)which then callsZoomInOutI have to call
ZoomInOutfrom the main C4D thread becauseDrawViewscan 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
CommandDatabut 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
Messagefunction.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