SpecialEventAdd unpredictable timing to GUI::Command
-
Hi everybody,
I have a
class GUI : public GeDialog
class 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
for
loop and when it finishes it callsSpecialEventAdd
and then starts anotherfor
loop, but my message isn't in caught inbool GUI::Command
until after the for secondfor
loop finishes. Is there anyway to make the main thread wait for theSpecialEventAdd
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 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
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. - 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
ZoomInOut
function i want theSpecialEventAdd();
message to be caught byGUI::CoreMessage
before the secondfor
loop in inZoomInOut
starts.In
GUI::CoreMessage
I catch theSpecialEventAdd();
message and callSetString(DLG_DYNAMIC_RENDER_TEXT, "test changed text"_s);
which does change theAddStaticText
dialog on my panel, but it doesnt update until after the secondfor
loop.- 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::CoreMessage
which callsAddStaticText()
and then would continue afterSpecialEventAdd();
where more draw calls are. But since I think your right about a loop in C4D itself theGUI::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 ofTestClass::ZoomInOut
rather 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 theGeDialog
method that calledZoomInOut
).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 - 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
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
.
YesBool GUI::CoreMessage(Int32 id, const BaseContainer &bc)
in inmain.cpp
I added to the code to show there is a button in
GUI::CreateLayout()
that callsGUI::Command(Int32 id, const BaseContainer &msg)
which then callsZoomInOut
I have to call
ZoomInOut
from the main C4D thread becauseDrawViews
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