Author Topic: Using Transactions In LISP  (Read 8941 times)

0 Members and 1 Guest are viewing this topic.

Peter Jamtgaard

  • Guest
Using Transactions In LISP
« on: December 09, 2013, 11:30:59 AM »
I had several interesting conversations with other programming colleagues at AU last week and I brought up the conversation about using Transactions in LISP.

What I found out was you can.

Exposed through the lispfunction method of .net you can create a transaction at the beginning of a command and commit or dispose of it at the end... It will roll back the command if disposed...

So I tried to create a way to change an object with a .net call to the top level transaction.

It works twice as fast as converting a entity to a vla-object and changing the object with the com.

Do you have a recommendation on testing software for lisp function speed?

I really think that a good .net dll could be developed to extend the power of lisp with .net.

I am always looking for suggestions of good functions...

Comments?

Peter

ymg

  • Guest
Re: Using Transactions In LISP
« Reply #1 on: December 09, 2013, 12:00:23 PM »
peter,

Not sure I follow you completely but it sounds very interesting.

Do you have any example maybe in pseudocode to light up my lantern?

ymg

ur_naz

  • Newt
  • Posts: 68
  • Made in Ukraine
Re: Using Transactions In LISP
« Reply #2 on: December 09, 2013, 12:29:26 PM »
There is no need to use backdoor in lisp. It is just inside AutoCAD. :-)

Peter Jamtgaard

  • Guest
Re: Using Transactions In LISP
« Reply #3 on: December 09, 2013, 04:46:46 PM »
I am not sure what you mean by backdoor.

To me good coding bypasses the command pipe and makes changes directly to the object model using com or some other methodology.

In this case, I want to be able to make multiple changes to the database, and avoid updating the database until I am done making the changes.

Using COM it updates the database on every property change.

Hence the reason to us transaction based changes, exposed from .net.

To manipulate the database from .net you need to use transactions.

That is one of the reasons that .net code is faster at runtime.

You could have 10000 changes and only one update of the database.

I am talking about adding transaction based manipulation of the database (like .net does) from lisp.

Instead of using (vlax-put objItem "color" 1)

Use these new lisp commands defined from .net

(transactionstart)
(propertyput entItem "colorindex" 1)
(transactioncommit)

Now if you only have one calls it probably is a wash, but if you have 10000 calls the transaction methodology would be MUCH faster.

p=


LE3

  • Guest
Re: Using Transactions In LISP
« Reply #4 on: December 09, 2013, 10:37:49 PM »
Sounds interesting...

Are those three LispFunction's, no?
 
On the PropertyPut - what are you using internally? for the property names? - Reflection? or have each of the Entity class basic properties names inside of a switch, list etc?
Can you pass any property name argument?
Or do you have to make for each of the desired properties a new method?

Glad, that you are taking the advantage of .NET.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: Using Transactions In LISP
« Reply #5 on: December 10, 2013, 12:10:18 AM »
Good idea! Though I can think of one snag: When you start a transaction you generally open one or more entities for write access. Since Lisp is running in the GUI thread, this means that those entities would then be unavailable to lisp - different thread (see it as if it's a different user wanting to change the objects which another user has open).

You might need to pass some ID code back-n-forth so the modify functions "know" to use the same thread in which the transaction was started.

Then again, I might be wrong - I've not tried sharing a transaction even between different DotNet programs, never mind between DotNet and Lisp. Only way to know for sure is to test it.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Using Transactions In LISP
« Reply #6 on: December 10, 2013, 12:39:16 AM »
Irne,
I don't imagine the algorithm design would allow user interaction while the transaction was active.

Gut level feel ;  I wouldn't allow the lisp to hold the transaction open between calls , even if it could be done simply. Cleaning up after an error may make it not worth the error.

Wish I knew what AutoDesk's intention was regarding AutoLisp delelopment and regarding Mac development and regarding AutoCAD 360
 ... but that IS asking a lot.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: Using Transactions In LISP
« Reply #7 on: December 10, 2013, 02:41:53 AM »
Irne,
I don't imagine the algorithm design would allow user interaction while the transaction was active.
Sorry, I think you misunderstood. I was using the 2 users working on the same thing as an analogy. The API sees 2 threads in the same light - that's why you sometimes get those issues where you can't edit something within a reactor (i.e. the reactor already has a read-lock on the entity, so your call-back function is not allowed to create a write-lock on the same entity when the reactor calls it).
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: Using Transactions In LISP
« Reply #8 on: December 10, 2013, 02:49:01 AM »
Gut level feel ;  I wouldn't allow the lisp to hold the transaction open between calls , even if it could be done simply. Cleaning up after an error may make it not worth the error.
That is the main principle behind transactions: i.e. if an error occurs your lisp code should catch it through the *error* function and call the transaction's dispose method so it rolls-back any changes which has already been done. Else the transaction is committed when no error occurs, thus the database is updated only when the function has completed correctly.

True it is dangerous to leave the transaction open, but what else to do? You can't hold the programmer's hands all the time, they need to think for themselves too  :lmao:

Perhaps use something like DotNet's "using" idea: Create a function which takes a list of lisp cells for execution, then starts the transaction, runs through that lisp list and ensures it closes the transaction (no matter what the lisp inside has done or not).
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Using Transactions In LISP
« Reply #9 on: December 10, 2013, 03:19:40 AM »

irne, I understand the purpose of a transaction.

Frankly, customisers currently don't make use of error trapping adequately .. I'd be bold enough to suggest that the majority don't understand how they work.
You're suggesting that another level of complication be added into the mix.

For me, it would be more beneficial adding .net functions that take lisp association lists of the keys,values to be changed and do all the grunt work in .net .. that way the complete operation can be wrapped without issue.

I'd consider leaving transactions open for a period of time not under the designers control is asking for trouble. 


//................
Peter,
If you're making changes to thousands  of objects often enough that the time taken is an issue you're using the wrong language to start with.
... getting expectations and capability mixed up in my opinion.

I thinks it's just an interesting intellectual exercise.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: Using Transactions In LISP
« Reply #10 on: December 10, 2013, 05:00:29 AM »
Frankly, customisers currently don't make use of error trapping adequately .. I'd be bold enough to suggest that the majority don't understand how they work.
You're suggesting that another level of complication be added into the mix.
I'm with you on that yes. Too many LSP's which cause stuff like OSMode / Highlight to go all wonky. Same thing could happen with these transactions I guess: i.e. user presses Esc, lisp fails to dispose/commit transaction ---->> user's unable to continue working in DWG as he's unable to modify anything until the transaction is ended (which will never end).

I'm just of the opinion, you shouldn't need to hamstring a language just because some (or even the great majority) of the "programmers" using it aren't doing it properly. At what point do you start thinking - this is too much power given to the programmer, versus where do you stop trying to make the language fool proof? Where you've turned all error handling into something synonymous with garbage collection or where you let the programmer handle all items which should be reset on an error?

For me, it would be more beneficial adding .net functions that take lisp association lists of the keys,values to be changed and do all the grunt work in .net .. that way the complete operation can be wrapped without issue.
That's a good idea too - though it's actually already implemented through ARX in the entmod routine for one entity at a time. This would basically be something like an entmod-multiple: i.e. send the function a list of dxf-data lists.

Actually this is where I think transactions could actually make Lisp more fool-proof. Rather than having the Lisp programmer write a full *error* defun which reverts all the modified items back to their original state - they now only need to call the single dispose on the transaction.

Or  :police: it might even be more advisable (read: More Fool Proof) to have a function which works like the using clause: Send the function a list of lisp instructions to perform on the list of entities. Then that DotNet function can wrap it all inside a transaction and roll back if it reaches an error. I.e. you implement the lisp eval calls inside a try-catch-finally block so you can ensure that (no matter what error's occurred) the transaction (and whatever other setting / reference you deem necessary) is rolled-back gracefully without the lisp programmer even needing to know about it.
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Using Transactions In LISP
« Reply #11 on: December 10, 2013, 09:27:35 AM »
The code I posted will have unexpected results if UNDO is called. It will roll back to state before previous command was called.
See Comment Above.
-------------------Edit---------------

You would of course include error checking, abstract out the transaction part to be reusable, have well defined return values, etc......


Reflection will not give you great performance but you could do it that way


First one selected a circle and second one selected a line.
Quote
Command: (SetProps (car (entsel)) "Layer" "0" "ColorIndex" 4 "Radius" 10)
Select object: (0)

Command: (SetProps (car (entsel)) "Layer" "0" "ColorIndex" 4 "Radius" 10)
Select object: (nil)


The code above took 3 times as long to write code below. Did know how to get the ename or Objectid from entsel

It Worked.
Code - C#: [Select]
  1.         [LispFunction("SetProps")]
  2.         public ResultBuffer SetProps(ResultBuffer buffer)
  3.         {
  4.  
  5.             TypedValue[] args = buffer.AsArray();
  6.             int numberOfArguments = args.Length;
  7.             if (numberOfArguments > 2 & numberOfArguments % 2 != 0)
  8.             {
  9.                 ObjectId id = (ObjectId)args[0].Value;
  10.  
  11.                 try
  12.                 {
  13.                     using (Transaction trx = Doc.TransactionManager.StartTransaction())
  14.                     {
  15.                         DBObject dbo = trx.GetObject(id, OpenMode.ForWrite);
  16.                         Type type = dbo.GetType();
  17.                         PropertyInfo propInfo;
  18.                         string propName;
  19.                         for (int i = 1; i < numberOfArguments; i = i + 2)
  20.                         {
  21.  
  22.                             propName = args[i].Value as string;
  23.                             propInfo = type.GetProperty(propName);
  24.                             propInfo.SetValue(dbo, args[i + 1].Value, null);
  25.                         }
  26.                         trx.Commit();
  27.                     }
  28.                 }
  29.                 catch (System.Exception)
  30.                 {
  31.                     return new ResultBuffer(new TypedValue((int)LispDataType.Nil));
  32.  
  33.                 }
  34.  
  35.  
  36.             }
  37.  
  38.             return new ResultBuffer(new TypedValue((int)LispDataType.Int16, 0));
  39.         }
  40.  


« Last Edit: December 12, 2013, 01:19:34 AM by Jeff H »

LE3

  • Guest
Re: Using Transactions In LISP
« Reply #12 on: December 10, 2013, 09:35:28 AM »
^
A quick sample can be (no Reflection used) I am curios of what he used to pass the property name(s) or if it is target to each particular names:

Code - C#: [Select]
  1.         const int RSRSLT = 1; /* Result returned */
  2.         const int RSERR = 3; /* Error in evaluation -- no result */
  3.  
  4.         // Type of resbuf element
  5.         const int RTNONE = 5000; /* No result */
  6.         const int RTREAL = 5001; /* Real number */
  7.         const int RTPOINT = 5002; /* 2D point X and Y only */
  8.         const int RTSHORT = 5003; /* Short integer */
  9.         const int RTANG = 5004; /* Angle */
  10.         const int RTSTR = 5005; /* String */
  11.         const int RTENAME = 5006; /* Entity name */
  12.         const int RTPICKS = 5007; /* Pick set */
  13.         const int RTORINT = 5008; /* Orientation */
  14.         const int RT3DPOINT = 5009; /* 3D point - X, Y, and Z */
  15.         const int RTLONG = 5010; /* Long integer */
  16.         const int RTVOID = 5014; /* Blank symbol */
  17.         const int RTLB = 5016; /* list begin */
  18.         const int RTLE = 5017; /* list end */
  19.         const int RTDOTE = 5018; /* dotted pair */
  20.         const int RTNIL = 5019; /* nil */
  21.         const int RTDXF0 = 5020; /* DXF code 0 for ads_buildlist only */
  22.         const int RTT = 5021; /* T atom */
  23.         const int RTRESBUF = 5023; /* resbuf */
  24.         const int RTMODELESS = 5027; /* interrupted by modeless dialog */
  25.  
  26.         // Error return code
  27.         const int RTNORM = 5100; /* Request succeeded */
  28.         const int RTERROR = -5001; // Some other error
  29.         const int RTCAN = -5002; // User cancelled request -- Ctl-C
  30.         const int RTREJ = -5003; // AutoCAD rejected request -- invalid
  31.         const int RTFAIL = -5004; // Link failure -- Lisp probably died
  32.         const int RTKWORD = -5005; // Keyword returned from getxxx() routine
  33.         const int RTINPUTTRUNCATED = -5008; // Input didn't all fit in the buffer
  34.  

Code - C#: [Select]
  1.         [LispFunction("PutProperty")]
  2.         public void Swamp_PutProperty(ResultBuffer resultBuffer)
  3.         {
  4.             if (resultBuffer == null || resultBuffer.AsArray().Any(tv => tv.TypeCode != RTENAME && tv.TypeCode != RTSTR && tv.TypeCode != RTSHORT)) return;
  5.             var id = (ObjectId)resultBuffer.AsArray().FirstOrDefault(tv => tv.TypeCode == RTENAME).Value;
  6.             if (id == ObjectId.Null) return;
  7.             var propertyName = (string)resultBuffer.AsArray().FirstOrDefault(tv => tv.TypeCode == RTSTR).Value;
  8.             var propertyValue = (short)resultBuffer.AsArray().FirstOrDefault(tv => tv.TypeCode == RTSHORT).Value;
  9.             using (var transaction = AcadApp.DocumentManager.MdiActiveDocument.Database.TransactionManager.StartTransaction())
  10.             {
  11.                 var entity = transaction.GetObject(id, OpenMode.ForWrite) as Entity;
  12.                 if (entity != null)
  13.                 {
  14.                     switch (propertyName.ToUpper())
  15.                     {
  16.                         case "LINEWEIGHT":
  17.                             break;
  18.                         case "VISIBLE":
  19.                             break;
  20.                         case "COLORINDEX":
  21.                             entity.ColorIndex = propertyValue;
  22.                             break;
  23.                     }
  24.                 }
  25.                 transaction.Commit();
  26.             }
  27.         }
  28.  

divtiply

  • Guest
Re: Using Transactions In LISP
« Reply #13 on: December 10, 2013, 10:54:54 AM »
I had several interesting conversations with other programming colleagues at AU last week and I brought up the conversation about using Transactions in LISP.

Take a look at Bricsys LispEx's:

vle-start-transaction
starts a database transaction; returns t on success
(vle-start-transaction)
only 1 transaction level is supported, nested calls have no effect

vle-end-transaction
finishes a previously started database transaction
(vle-end-transaction)
must be paired with (vle-start-transaction), even when nested; (vle-end-transaction) on a nested transaction level has no effect, only the outer-most (and only) transaction is finished; Lisp engine automatically ends an active transaction, i.e. in case or error, or when Lisp execution at top-level has finished

LE3

  • Guest
Re: Using Transactions In LISP
« Reply #14 on: December 10, 2013, 02:32:39 PM »
I just noticed that on autocad 2014 (windows - vanilla) you have some new autolisp (built-in) functions and in there is this one - btw:

http://docs.autodesk.com/ACDMAC/2012/ENU/filesALRMac/GUID-ED406069-97F9-4552-BBEF-9A02D06E6C1-203.htm

So, one can use:
Code: [Select]
(setpropertyvalue (car (entsel)) "color" 1)