Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Debugger says: You MUST NOT NEVER EVER use a modal dialog in a timer

    Cinema 4D SDK
    r20 r21 c++ maxon api classic api macos
    3
    24
    16.0k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • fwilleke80F
      fwilleke80
      last edited by

      Thank you, Manuel!

      1 Reply Last reply Reply Quote 0
      • fwilleke80F
        fwilleke80
        last edited by fwilleke80

        Hmm, any hints on how one might do this?
        I looked at the Jobs Manual in the SDK, but didn't quite get it.

        I guess it would work somehow like this:

        struct Credentials
        {
        	String username;
        	String password;
        };
        
        // Ask the user for credentials, so we can acquire a new access token
        class GetCredentialsJob : public maxon::JobInterfaceTemplate<GetCredentialsJob, Credentials>
        {
        public:
        	GetCredentialsJob() { };
        
        	// function operator with the actual workload
        	maxon::Result<void> operator ()()
        	{
        		Credentials credentials;
        		GetUserCredentials(credentials.username, credentials.password);
        
        		// store result
        		return SetResult(credentials); // ERROR: Rvalue reference to type 'Credentials' cannot bind to lvalue of type 'Credentials'
        	}
        };
        
        auto job = GetCredentialsJob::Create();
        job.Enqueue(maxon::JobQueueInterface::GetMainThreadQueue()); // ERROR: No member named 'Enqueue' in ...
        
        // Wait for job to finish, then somehow get the results and do something
        if (job.Wait()) // ERROR: No member named 'Wait' in ...
        {
        	Credentials credentials = job.GetResult(); // ERROR: No member named 'GetResult' in ...
        	// ...
        

        This seems like an awful lot of code, just to open a simple dialog and get two strings from it. And since it throws a number of errors, it's not even finished yet.

        1 Reply Last reply Reply Quote 0
        • ManuelM
          Manuel
          last edited by Manuel

          hello,

          you can pass a lambda function directly as suggest other enqueue method definitions

          That should be a bit less code.

          maxon::JobRef::Enqueue(
          	[]() -> maxon::Result<void>
          	{
          			String res = "temporary";
          		if (!RenameDialog(&res))
          			return maxon::UnknownError(MAXON_SOURCE_LOCATION);
          		ApplicationOutput("you new password is @", res);
          		return maxon::OK;
          	}
          	, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
          
          	
          

          You can send a message on your lambda that will update your plugins (i don't know how you are handling your licences).

          Cheers,
          Manuel

          MAXON SDK Specialist

          MAXON Registered Developer

          1 Reply Last reply Reply Quote 0
          • fwilleke80F
            fwilleke80
            last edited by

            Ah, great! Yes, that looks a lot more compact, thank you!

            1 Reply Last reply Reply Quote 0
            • fwilleke80F
              fwilleke80
              last edited by fwilleke80

              Hmm, referring to your code example, I am doing it like this now:

              // Ask the user for credentials, so we can acquire a new access token
              String username, password;
              Bool dialogResult;
              
              maxon::JobRef ref = maxon::JobRef::Enqueue(
              			[&username, &password, &dialogResult] () -> Bool
              			{
              				dialogResult = GetUserCredentials(username, password);
              				return dialogResult;
              			}, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
              
              // Wait until dialog is closed
              ref.Wait(); 
              
              // If user clicked "OK"
              if (dialogResult)
              {
              	// do stuff
              	// ...
              

              It does not help, sorry.

              1. Cinema freezes and never wakes up again. The dialog does not appear.
              2. If I don't do the ref.Wait(), Cinema does not freeze, but it also does not wait for the user to close the dialog.
              3. How do I even return the result of GetUserCredentials() ? The lambda has Bool as return type, but JobRef::GetResult() only returns a maxon::Result<void>. Therefore, I'm returning the value via a reference now.
              4. Worst of all, the warning You MUST NOT NEVER EVER use a modal dialog in a timer. Please run a job on the main thread (see job.h). still appears!!

              Is there anything else I can try?

              Thanks & greetings,
              Frank

              1 Reply Last reply Reply Quote 0
              • ManuelM
                Manuel
                last edited by Manuel

                hi,

                why you don't put the "todo stuff" inside your lambda ?
                here, my function pc12545 is called by a command Data.

                class pc12545_nodalDialog : public GeModalDialog
                {
                
                public:
                	Bool CreateLayout(void) override
                	{
                		AddEditText(1000, BFH_LEFT, SizePix(150));
                		AddButton(1001, BFH_LEFT, 50, 20, "OK"_s);
                		return true;
                	}
                
                
                	Bool InitValues(void) override
                	{
                		SetString(1000, "temporary"_s);
                		return true;
                	}
                
                
                	Bool AskClose(void) override
                	{
                		return false;
                	}
                
                
                	Bool Command(Int32 id, const BaseContainer& msg) override
                	{
                
                		if (id == 1001)
                		{
                			GetString(1000, _password);
                			if (_password.IsPopulated())
                				Close(true);
                			else
                				MessageDialog("Please Enter A password"_s);
                		}
                
                		return true;
                	}
                
                	maxon::Result<maxon::String> GetPass() const
                	{
                		if (_password.IsPopulated())
                			return _password;
                
                		return maxon::UnknownError(MAXON_SOURCE_LOCATION);
                	}
                
                private:
                	maxon::String _password = ""_s;
                
                };
                
                
                static maxon::Result<void> pc12545(BaseDocument* doc)
                {
                	iferr_scope;
                
                	
                 maxon::JobRef::Enqueue(
                	[]() -> maxon::Result<void>
                	{
                		 iferr_scope;
                			pc12545_nodalDialog credentialsDialog;
                			credentialsDialog.Open(-1, -1, 200, 200, true);
                
                			if (credentialsDialog.GetResult())
                			{
                				const	maxon::String returnedString = credentialsDialog.GetPass() iferr_return;
                				ApplicationOutput("you new password is @", returnedString);
                				
                			}
                			else
                			{
                				ApplicationOutput("your password haven't been modified");
                				return maxon::UnknownError(MAXON_SOURCE_LOCATION);
                			}
                                        // ... do something with the password.CoreMessage maybe.
                			return maxon::OK;
                			
                	}
                	, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
                
                	
                
                	return maxon::OK;
                }
                

                Cheers,
                Manuel

                MAXON SDK Specialist

                MAXON Registered Developer

                1 Reply Last reply Reply Quote 0
                • fwilleke80F
                  fwilleke80
                  last edited by

                  This post is deleted!
                  1 Reply Last reply Reply Quote 0
                  • fwilleke80F
                    fwilleke80
                    last edited by fwilleke80

                    Hmm, I can do the "to do" stuff inside the lambda, of course. But what would that change? The problem is, still, that the code is being called from MessageData::CoreMessage() (inside a CommandData, like in your example, was never a problem), and therefore opening the dialog causes the warning to appear, no matter what code comes after opening it.

                    And the other problem still persists, too: If I don't Wait() after enqueueing the lambda, the dialog doesn't even appear. And if I Wait(), Cinema freezes.

                    Maybe it's simply not possible to do anything with modal dialogs in code that's called from MessageData::CoreMessage, regardless of any threading stunts we could do?

                    1 Reply Last reply Reply Quote 0
                    • ManuelM
                      Manuel
                      last edited by

                      hi,
                      using wait or getresult is really strange i agree, but you don't need them.

                      I've added a MessageData and use a timer to trigger the CoreMessage. There i enqueue de job that is executed immediately,

                      The following code is not executed because g_inTimer is false. (the job is executed from main thread).

                      	if (g_inTimer)
                      	{
                      		DiagnosticOutput("You MUST NOT NEVER EVER use a modal dialog in a timer. Please run a job on the main thread (see job.h).");
                      	}
                      

                      the MessageData i've added :

                      class TimerMessage_pc12545 : public MessageData
                      {
                      	virtual Int32 GetTimer(void);
                      	virtual Bool CoreMessage(Int32 id, const BaseContainer &bc);
                      };
                      
                      Int32 TimerMessage_pc12545::GetTimer()
                      {
                      	return 10000;
                      }
                      
                      Bool TimerMessage_pc12545::CoreMessage(Int32 id, const BaseContainer &bc)
                      {
                      	if (id == MSG_TIMER)
                      	{
                      		iferr_scope_handler
                      		{
                      			return false;
                      
                      		};
                      
                      		maxon::JobRef::Enqueue(
                      			[]() -> maxon::Result<void>
                      			{
                      				iferr_scope;
                      				pc12545_nodalDialog credentialsDialog;
                      				credentialsDialog.Open(-1, -1, 200, 200, true);
                      
                      				if (credentialsDialog.GetResult())
                      				{
                      					const	maxon::String returnedString = credentialsDialog.GetPass() iferr_return;
                      					ApplicationOutput("you new password is @", returnedString);
                      
                      				}
                      				else
                      				{
                      					ApplicationOutput("your password haven't been modified");
                      					return maxon::UnknownError(MAXON_SOURCE_LOCATION);
                      				}
                      				return maxon::OK;
                      				// ... do something with the password.
                      			}
                      		, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
                      
                      
                      	}
                      
                      	return true;
                      }
                      

                      Cheers,
                      Manuel

                      MAXON SDK Specialist

                      MAXON Registered Developer

                      1 Reply Last reply Reply Quote 0
                      • fwilleke80F
                        fwilleke80
                        last edited by fwilleke80

                        Hm, thanks. Well, in my case, I don't even use the MessageData's timer. I am simply reacting to a CoreMessage that is sent from another part of the plugin. And it seems, in that case g_inTimer is true. Is there a way to make it false?

                        My code looks basically similar to yours, I'm just listening to another message, not MSG_TIMER.

                        1 Reply Last reply Reply Quote 0
                        • fwilleke80F
                          fwilleke80
                          last edited by

                          Addition: I've also tried returning 0 from MessageData::GetTimer(), it changes nothing.

                          1 Reply Last reply Reply Quote 0
                          • fwilleke80F
                            fwilleke80
                            last edited by

                            And 2nd addition: How do you proceed when you actually need the credentials from the dialog in the code after the lambda? I can't move the whole plugin with all its components to a separate enqueued job, just because of the problem with modal dialogs.

                            I need to show the user the dialog, and get their input. I need things to wait until the user has closed the dialog, then I need the credentials and do stuff with it that is not part of the same lambda.

                            1 Reply Last reply Reply Quote 0
                            • ManuelM
                              Manuel
                              last edited by

                              hi,
                              I don't think the message i react to is important.

                              You can call whatever function you need inside the lambda. You can pass that function some parameters. This will be executed from the mainThread.
                              Once it's done, the lambda will send a CoreMessage and your plugins could react to that.

                              it's just like "hey job, do this for me and tell me once it's done"

                              Sorry i don't see where's the issu here, or at least i can't reproduce it.
                              Maybe you could send us some more information by email to keep it secret (as it's related to password etc) or code.

                              Cheers,
                              Manuel

                              MAXON SDK Specialist

                              MAXON Registered Developer

                              1 Reply Last reply Reply Quote 0
                              • fwilleke80F
                                fwilleke80
                                last edited by

                                Sending another CoreMessage... interesting... I will try that, too. Thank you!

                                1 Reply Last reply Reply Quote 0
                                • ManuelM
                                  Manuel
                                  last edited by

                                  hi,

                                  did you tried out ?

                                  Cheers,
                                  Manuel

                                  MAXON SDK Specialist

                                  MAXON Registered Developer

                                  1 Reply Last reply Reply Quote 0
                                  • ManuelM
                                    Manuel
                                    last edited by

                                    hi,

                                    I'll set this thread as solved without feedback from your side.

                                    Cheers,
                                    Manuel

                                    MAXON SDK Specialist

                                    MAXON Registered Developer

                                    1 Reply Last reply Reply Quote 0
                                    • fwilleke80F
                                      fwilleke80
                                      last edited by

                                      I did try it out, and did not experience any change in behavior. I guess it's rooted somewhere entirely else.

                                      1 Reply Last reply Reply Quote 0
                                      • ManuelM
                                        Manuel
                                        last edited by

                                        arff, that's really strange, I'll send you a simple example and see if the error still happen on your system.

                                        MAXON SDK Specialist

                                        MAXON Registered Developer

                                        1 Reply Last reply Reply Quote 0
                                        • First post
                                          Last post