Author Topic: Editor.Command() vs Editor.CommandAsync()  (Read 26394 times)

0 Members and 1 Guest are viewing this topic.

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Editor.Command() vs Editor.CommandAsync()
« on: March 22, 2015, 12:21:16 PM »
Hi,

Since AutoCAD 2015, two instance methods have been added to the Editor class: Editor.Command() and Editor.CommandAsync().
These method replace the need to P/Invoke acedCmd() or call the non-public Editor.RunCommand() to synchronously run AutoCAD commands.
Note that these methods are no longer available.

The docs about these methods are more than laconic: "This is Command, a member of class Editor."
Googling for CommandAsync I only found 3 examples: a thread on Kean Walmsley's blog, another one from AutoCAD DevBlog and a code sample on GitHub.
So, I made some tests to try to deeply understand how work these new methods and share the results here.


About the methods arguments type

Both Editor.Command() and Editor.CommandAsync() accept a params array of object as argument. This means the arguments (the command name and the command inputs) can be passed separated by a comma (no need to build an array as in the examples on the Adn DevBlog and GitHub).
The command inputs can be any strong .NET type (int, double, Point2d, Point3d, ObjectId, SelectionSet, ...).
As with the LISP function command, numbers or points can be passed as string (as on command line).
An empty string ("") stands for Enter and backslash (@"\") for a pause for user input. For the last one, a constant have added to the Editor class : Editor.PauseToken.


Command() vs CommandAsync()

That said, the main question is: what is the difference between Command() and CommandAsync(), in other words, when must we have to use CommandAsync() rather than Command().
This seems to be the purpose of Kean's example, but I do not think it is very relevent: the Editor.Command() also accepts pauses for user input.

Going a little further in the comparison with de command LISP function, I tried to mimic some LISP behaviors:
- call multiple commands in a single expression:
Code - Auto/Visual Lisp: [Select]
  1. (command "_.ZOOM" "_object" (entlast) "" "_ZOOM" ".8x")
- split the the command arguments in multiple expressions:
Code - Auto/Visual Lisp: [Select]
  1. (command "_.CIRCLE")
  2. (command "0,0")
  3. (command "10")

In both cases, it requires to use CommandAsync(). With Command() method, we have to call it for each command (the following ZoomEntLast() method is also used in later examples)
Code - C#: [Select]
  1.         public void ZoomEntLast()
  2.         {
  3.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  4.             ObjectId entlast = Autodesk.AutoCAD.Internal.Utils.EntLast();
  5.             if (entlast != ObjectId.Null)
  6.             {
  7.                 ed.Command("_.ZOOM", "_object", entlast, "");
  8.                 ed.Command("_.ZOOM", ".8x");
  9.             }
  10.         }
  11.  
  12.         [CommandMethod("CMD1")]
  13.         public async void Cmd1()
  14.         {
  15.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  16.             await ed.CommandAsync(
  17.                 "_.LINE", new Point3d(-10.0, -10.0, 0.0), new Point3d(10.0, 10.0, 0.0), "",
  18.                 "_.CIRCLE", Point3d.Origin, 10.0);
  19.             ZoomEntLast();
  20.         }
  21.  
  22.         [CommandMethod("CMD2")]
  23.         public async void Cmd2()
  24.         {
  25.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  26.             await ed.CommandAsync("_.CIRCLE");
  27.             await ed.CommandAsync("0,0");
  28.             await ed.CommandAsync("10");
  29.             ZoomEntLast();
  30.         }

The last shown behavior is interesting, for example, to pass a list of arguments to a command (e.g. a points list for LINE, PLINE, SPLINE).
This also requires the CommandAsync using.
Code - C#: [Select]
  1.         [CommandMethod("CMD3")]
  2.         public async void Cmd3()
  3.         {
  4.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  5.             Point2d[] pts =
  6.             {
  7.                 new Point2d(0.0, -10.0),
  8.                 new Point2d(10.0, 0.0),
  9.                 new Point2d(0.0, 10.0),
  10.                 new Point2d(-10.0, 0.0)
  11.             };
  12.             await ed.CommandAsync("_.PLINE");
  13.             foreach (Point2d pt in pts)
  14.             {
  15.                 await ed.CommandAsync(pt);
  16.             }
  17.             await ed.CommandAsync("_close");
  18.             ZoomEntLast();
  19.         }


Synchronicity

Another "advanced using of command" in LISP is to launch a command and let the user complete it before continuing the program.
Code - Auto/Visual Lisp: [Select]
  1.   (command "_.PLINE")
  2.   (while (< 0 (getvar "CMDACTIVE"))
  3.     (command pause)
  4.   )

With the Editor.Command() method, all the 'while stuff' is implicit, and the program will wait for the command completed:
Code - C#: [Select]
  1.         [CommandMethod("CMD4")]
  2.         public void Cmd4()
  3.         {
  4.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  5.             ed.Command("_.PLINE");
  6.             ZoomEntLast();
  7.         }

To be synchronous, the Editor.CommandAsync() needs to explicitly complete the command (iow do the while stuff as in LISP). This may be interesting if some other task may interact during the user inputs, for example, collecting the newly created lines:
Code - C#: [Select]
  1.         [CommandMethod("CMD5")]
  2.         public async void Cmd5()
  3.         {
  4.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  5.             HashSet<ObjectId> ids = new HashSet<ObjectId>();
  6.  
  7.             await ed.CommandAsync("_.LINE", Editor.PauseToken);
  8.             while (((string)Application.GetSystemVariable("CMDNAMES")).Contains("LINE"))
  9.             {
  10.                 try
  11.                 {
  12.                     await ed.CommandAsync(Editor.PauseToken);
  13.                     ids.Add(Autodesk.AutoCAD.Internal.Utils.EntLast());
  14.                 }
  15.                 catch { break; } // eUserBreak (Cancel) handling
  16.             }
  17.  
  18.             Database db = HostApplicationServices.WorkingDatabase;
  19.             using (Transaction tr = db.TransactionManager.StartTransaction())
  20.             {
  21.                 foreach (ObjectId id in ids)
  22.                 {
  23.                     Entity ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);
  24.                     ent.ColorIndex = 1;
  25.                 }
  26.                 tr.Commit();
  27.             }
  28.         }
Speaking English as a French Frog

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #1 on: March 22, 2015, 02:05:39 PM »
Thank you, gile. It is useful and interesting.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #2 on: March 23, 2015, 12:23:54 PM »
Hi Gile,


Thank you for the information.  It was a good read.  I do not know about anyone else but I would certainly be interested in helping fund a kickstarter campaign for you to start writing a book on C# and the AutoCAD .net API!  It is something for you to seriously think about!
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #3 on: March 23, 2015, 02:16:04 PM »
Hi Gile,


Thank you for the information.  It was a good read.  I do not know about anyone else but I would certainly be interested in helping fund a kickstarter campaign for you to start writing a book on C# and the AutoCAD .net API!  It is something for you to seriously think about!

Second That!
Revit 2019, AMEP 2019 64bit Win 10

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #4 on: March 23, 2015, 02:20:09 PM »
I do not know about anyone else but I would certainly be interested in helping fund a kickstarter campaign for you to start writing a book on C# and the AutoCAD .net API!
What book? Is exist some content of this?

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #5 on: March 23, 2015, 02:38:53 PM »
Hi Gile,


Thank you for the information.  It was a good read.  I do not know about anyone else but I would certainly be interested in helping fund a kickstarter campaign for you to start writing a book on C# and the AutoCAD .net API!  It is something for you to seriously think about!

Second That!

Thank you very much both, this is very kind of you, but writing a book about AutoCAD .NET API is really very high over my head (even in French, my native language).
Anyway, I'm glad to share what I discover in the API regions I explore.
Speaking English as a French Frog

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #6 on: March 23, 2015, 03:18:06 PM »
What book? Is exist some content of this?


No book yet Andrey.  I am trying to convince gile to write one.  I would happily help fund it through a crowd sourcing campaign such as Kickstarter or something similar.


Gile,  I believe that you put too much emphasis on your "supposed" language handicap.  You explain things in english much better than I ever could and I have been speaking it my entire life.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #7 on: March 23, 2015, 03:31:58 PM »
I can't transfer money, but I can share my some materials for the interesting book, if good author will be appearing.

BlackBox

  • King Gator
  • Posts: 3770
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #8 on: March 23, 2015, 07:48:09 PM »
What book? Is exist some content of this?


No book yet Andrey.  I am trying to convince gile to write one.  I would happily help fund it through a crowd sourcing campaign such as Kickstarter or something similar.


Gile,  I believe that you put too much emphasis on your "supposed" language handicap.  You explain things in english much better than I ever could and I have been speaking it my entire life.

1+

This would be worth donating toward - and well done here, Gile.



FWIW - I requested Autodesk consider compensating Gile for his work on CTR custom OSNAP (.NET posted publicly here), and his OSNAPS Palette App currently published at Exchange Apps Store (as a result of the new 'independently developed' GCE feature (geometric center, aka "Gilles Chanteau Emulator") .

Attempted something similar in previous beta for Lee, for his LISP work on MText; not sure that either were successful offline, but I tried. *shrug*
"How we think determines what we do, and what we do determines what we get."

Kean

  • Newt
  • Posts: 48
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #9 on: March 24, 2015, 05:15:33 AM »
Great analysis, Gile!

I'd just add a quick note... for me the intention behind these two methods is that Command() be used to execute complete commands synchronously (with the well-taken caveat that any user prompting required to complete the command will get performed) while CommandAsync() allows the application code to participate further in the command's execution (including specifying where the user should provide input).

Kean

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #10 on: March 24, 2015, 05:39:12 AM »
Thanks Kean.

So, just a question: to let the user complete command inputs, is it safe to just call Command() as below (which seems to work fine) ?

Code - C#: [Select]
  1.             [CommandMethod("CMD4")]
  2.            public void Cmd4()
  3.            {
  4.                Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  5.                ed.Command("_.PLINE");
  6.                ZoomEntLast();
  7.            }

or is it preferable to call CommandAsync() ?

Code - C#: [Select]
  1.         [CommandMethod("CMD6")]
  2.         public async void Cmd6()
  3.         {
  4.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  5.             await ed.CommandAsync("_.PLINE");
  6.             while (((string)Application.GetSystemVariable("CMDNAMES")).Contains("PLINE"))
  7.             {
  8.                 try { await ed.CommandAsync(Editor.PauseToken); }
  9.                 catch { break; }
  10.             }
  11.             ZoomEntLast();
  12.         }
Speaking English as a French Frog

Kean

  • Newt
  • Posts: 48
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #11 on: March 24, 2015, 06:22:09 AM »
So, just a question: to let the user complete command inputs, is it safe to just call Command() as below (which seems to work fine) ?

Good question...

Editor.Command() calls through to acedCmdS(). It's a slightly different code path from acedCommandS(), for which the ObjectARX docs say:

"However, if acedCommandS is not suitable if you want to pass in enough tokens to begin a command but not finish it. When the tokens run out, any command in progress is cancelled. Partially completed command side effects are not rolled back."

Clearly that's not the behaviour we're seeing here, but my personal instinct would be to avoid relying on acedCmdS() completing partial commands (rather than cancelling them, as documented for acedCommandS()).

Kean

owenwengerd

  • Bull Frog
  • Posts: 451
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #12 on: March 24, 2015, 06:23:01 PM »
I believe the ARX documentation was correct for the early builds of AutoCAD 2015 (or maybe 2014?), but the implementation was subsequently changed (presumably because it was discovered that cancelling the command served no purpose except to break working code).

Kean

  • Newt
  • Posts: 48
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #13 on: March 25, 2015, 02:04:49 AM »
Thanks, Owen.

I'll ask around and see what I find out.

Kean

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Editor.Command() vs Editor.CommandAsync()
« Reply #14 on: March 25, 2015, 11:48:34 PM »
This is what I got out of it.

For starters I think the use Async, await might confuse or give some wrong impression like Dispose and Using statement with AutoCAD API.
Not that they misuse either but calling Dispose is usually in association with releasing unmanaged resources from memory. The API heavily deals with manipulating a AutoCAD database and the dispose calls are used to close database resident objects and you never actually delete or release memory of a database resident object. Only objects created that do not get added to a database where it then is handled by AutoCAD.
Maybe if explained in more detail how dispose is really calling close or cancel on database resident objects might help some at first.

CommandAsync calls acedcmdC
Command calls acedCmdS

Code - C#: [Select]
  1.   public unsafe void Command(params object[] parameter)
  2. {
  3.     resbuf* resbufPtr = 0L;
  4.     try
  5.     {
  6.         resbufPtr = (resbuf*) Marshaler.ObjectsToResbuf(parameter).ToPointer();
  7.         Interop.CheckAds(acedCmdS((resbuf modopt(IsConst)*) resbufPtr, false, 0L));
  8.     }
  9.     finally
  10.     {
  11.         if (resbufPtr != null)
  12.         {
  13.             acutRelRb(resbufPtr);
  14.         }
  15.     }
  16. }
  17.  
  18.  

Code - C#: [Select]
  1.  
  2.   public unsafe CommandResult CommandAsync(params object[] parameter)
  3. {
  4.     if (acedFiberWorld())
  5.     {
  6.         Interop.ThrowExceptionForErrorStatus(2);
  7.     }
  8.     resbuf* resbufPtr = 0L;
  9.     try
  10.     {
  11.         resbufPtr = (resbuf*) Marshaler.ObjectsToResbuf(parameter).ToPointer();
  12.         CommandResult result = new CommandResult();
  13.         IntPtr ptr = (IntPtr) GCHandle.Alloc(result);
  14.         Interop.CheckAds(acedCmdC((int modopt(CallConvCdecl) *(void*)) __unep@?acedCmdCallback@@$$FYAHPEAX@Z, ptr.ToPointer(), (resbuf modopt(IsConst)*) resbufPtr));
  15.         acedCallBackOnCancel();
  16.         return result;
  17.     }
  18.     finally
  19.     {
  20.         if (resbufPtr != null)
  21.         {
  22.             acutRelRb(resbufPtr);
  23.         }
  24.     }
  25.     return null;
  26. }
  27.  
  28.  
  29.  
  30.  


So a quick overview of possible behavior


acedCmdC vs acedCmdS
The "C" in acedCmdC stands for Coroutine and "S" in acedCmdS stands for Subroutine.
Very quick overview
Subroutine is invoked at beginning and runs until end or is exited from return, etc....
Coroutine is invoked and then exit calling another Coroutine, but save its state and return back to point and have multiple entry points.

Coroutines(language level) and  fibers(system level)
Coroutines are a construct at the language level. The same concept of control flow at the system level is fibers.

Fibers are depreciated in2015

AutoCAD used fibers for  document switching and command execution by swapping native code call stacks within a thread
Without fibers AutoCAD has only one code stack in its main thread.

So now to transfer control by pushing more code on the stack (subroutine style), or by returning off the call stack after queueing a message to the logic that will then be executed  (coroutine style).   

AutoCAD still operates under the concepts of “application context” and “document context”.   Except now, to reach “application context”, AutoCAD must return out of functions which are logically bound to documents, rather than perform a stack switch.

I believe Lisp processor handles itself by saving call stack, processes it, re-invokes and restores, etc... to work as it did before.

Some of the results I believe are from
If you look at docs at acedCommand Migration
Quote
// acedCommandS:  syntax-compatible replacement of acedCommand,
//                use for "full commands", not partial ones.
//                This command essentially begins with its own
//                private command line processor, initialized at
//                the "Command:" prompt.   The commands are executed
//                in the same manner as with acedCommand, except that
//                when the supplied input has all been executed,
//                the private command line processor is deleted
//                and the previous command line processor is unchanged
//                from when the call is made.   modality and
//                transparent "builtin" commands may not be nested.


// acedCmdS
//
// Does the same thing with the same restructions as acedCommandS,
// with a resbuf chain rather than a veriable arguments list.
// Two more supplied parameters are intended for future use.



// acedCommandC, acedCmdC:
// “coroutine” relationship with calling command processor.
// Queues up the resbuf chain for feeding to the
// calling command processor and stores the “returning”
// callback function pointer and data pointer.
// In the text here, they will be collectively
// referred to as "acedCmdC", which in fact is
// the common implementation.  "acedCmd" will represent
// the corresponding legacy command functions.
//
// After making call, caller must return from its
// command (or defun/regfunc) entry
// point to allow the command processor to process the
// pending command input.  Once done with that input,
// AutoCAD will call pReturnFcn, with pReturnFcnParams
// passed in.
//
// There are some common rules for the use of both acedCmdC
// and acedCmd within a recognized application callback
// (specifically, these functions are only operative within
// specific AutoCAD contexts:
// - a command, registered in ARX via AcEdCommandStack::addCommand
// - a LISP invocation of a function registered via acedDefun
//   (and/or acedRegfunc).
// - a coroutine callback passed in from acedCmdC
// - Evaluation of LISP (command) statement evaluation.
// - builtin commands (plus AcEdCommand commands with the
//   ACRX_CMD_NONEWSTACK flag set).
// - Session Fiber Commands (or Application Execution Context)
// - Transparent commands, executed at an input prompt of another
//   command in progress (Regardless of builtin or ARX-registered)
//
// 1. acedCmdC may be invoked from non-LISP calling contexts
//    regardless of the $FIBERWORLD
//    setting, (the exclusion from transparent commands remains).
//
//    From LISP contexts where acedInvoke() is active (i.e. acedDefun'ed
//    functions being called from LISP),
//    acedCmdC may only be invoked when $FIBERWORLD is 0.
//
//    acedCmd, in comparison, cannot be invoked unless $FIBERWORLD
//    is 1, if done, it will return an error status. It also
//    cannot be invoked from builtin commands, nor transparent commands.
//
//    Therefore, function logic running in an acedDefun/acedInvoke
//    context should test acedFiberWorld to determine whether
//    to run acedCmd or acedCmdC is permissible. Notice that
//    acedCmdS is safe to run in this context regardless of the
//    $FIBERWORLD setting.
//
// 2. While it is not a destination in one's migration,
//    Command and defun'ed function logic (but not coroutine callbacks,
//    described next) may invoke acedCmd any number of times,
//    but after making a call to acedCmdC, the function must
//    return without making a call to either acedCmdC nor acedCmd.
//    If such a call is made, the resbuf chain previously supplied
//    by acedCmdC will be replaced with the subsequent chain.
//    acedCmd will return an error status and take no action.
// 3. Coroutine continuation functions may call acedCmdC,
//    but use of acedCmd will return an error status
//    and no action.
// 2. acedCmdS may be used in any of these contexts (and many more)
//    and at any time within these contexts.
//    It is unobvious to invoke acedCmdS
//    after calling acedCmdC and before returning to have its
//    input procssed, since the acedCmdS will be executed
//    synchronously, but it would work.
// 3. Repeated calls to acedCmdC without returning for processing
//    in between will simply erase the earlier acedCmdC contents.
//    In other words, only one call will count before returning,
//    so don't bother making multiple calls.
// 4. Commands and LISP expressions which use different mixes of
//    acedCmd, acedCmdC and acedCmdS may invoke each other
//    without regard to the makeup of the other command/expression.
//    Command processor modality restrictions will still apply
//    such as no nesting of nontransparent commands nor transparent
//    commands.
//
// WARNING: support of these functions in logic operated through
//          invocation of functions registered via acedDefun
//          is incomplete in ACAD 2011.
//