Entities Successfully Added to Group But Not Visible in screen

Entities Successfully Added to Group But Not Visible in screen

rampseeker
Advocate Advocate
1,182 Views
14 Replies
Message 1 of 15

Entities Successfully Added to Group But Not Visible in screen

rampseeker
Advocate
Advocate


I created entity objects in another function, then opened a transaction to add these objects to a Group. The operation executed without any errors, and I confirmed that the entities exist in ModelSpace. However, they do not appear on the screen in AutoCAD.

 

Why are the cells not displayed in the drawing even though the code executed without any errors?

here is Example Code : 

 

using (Transaction tr = db.TransactionManager.StartTransaction())
{
    DBDictionary groupDict = (DBDictionary)tr.GetObject(db.GroupDictionaryId, OpenMode.ForWrite);
    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
    BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
    
    foreach (var result in cellResults)
    {
        List<Entity> cellEntities = result; 

        ObjectIdCollection objCollection = new ObjectIdCollection();
        foreach (Entity entity in cellEntities)
        {
            btr.AppendEntity(entity);
            DBObject dBObject = tr.GetObject(entity.ObjectId, OpenMode.ForRead);
            objCollection.Add(entity.ObjectId);
        }
        string groupName = "Group1";
        Group group = new Group(groupName, true);
        ObjectId groupId = groupDict.SetAt(groupName, group);
        group.Append(objCollection);
    }
    tr.Commit();
}

 

0 Likes
Accepted solutions (1)
1,183 Views
14 Replies
Replies (14)
Message 2 of 15

_gile
Consultant
Consultant

@rampseeker wrote:


I created entity objects in another function, then opened a transaction to add these objects to a Group.


You should also show the other function code because it might not be a good practice to get or create objects in a transaction and use these objects outside of the scope of this transaction.

You can either use the same transaction or use the ObjectIds of these objects.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 15

ActivistInvestor
Mentor
Mentor

You're not showing enough code. You are using entities that were created or opened outside of the code you show.

 

If those entities are new entities then you need to call AddNewlyCreatedDBObject() on the transaction passing each newly-added entity to it, and there's no need to reopen the entity using GetObject() because AppendEntity() returns the entity's ObjectId that you need to append it to the group.

 

If the entities you're adding to the group are not new entities, you need to show the code where they are instantiated.

0 Likes
Message 4 of 15

rampseeker
Advocate
Advocate

  First of all, let me explain why I wrote the code this way. Normally, I would create a polyline in this manner and then append it to a group, like this.

 

 

 

 

Polyline polyline = new Polyline();
polyline.AddVertexAt(0, new Point2d(vertex1.X, vertex1.Y), 0, 0, 0);
polyline.AddVertexAt(1, new Point2d(vertex2.X, vertex2.Y), 0, 0, 0);
polyline.AddVertexAt(2, new Point2d(vertex3.X, vertex3.Y), 0, 0, 0);
polyline.Closed = true;

btr.AppendEntity(polyline);
trans.AddNewlyCreatedDBObject(polyline, true);

DBDictionary groupDict = (DBDictionary)tr.GetObject(db.GroupDictionaryId, OpenMode.ForWrite);
string groupName = "Group_Example"
Group group = new Group(groupName, true);
group.Append(polyline.ObjectId);
r.AddNewlyCreatedDBObject(group, true);

 

 

 

 

  However, when the number of Entites to be drawn exceeds 10,0000, the number of objects managed by the transaction increases each time AddNewlyCreatedDBObject is called. As a result, the time required for tr.Commit becomes significantly longer.

  So, since simply creating polyline instances does not require a transaction, I tried creating the polyline objects separately and then saving them to the BlockTableRecord in a different transaction. Like this.

 

 

 

 

        List<Tuple<Polyline, DBText>> groupedEntities = new List<Tuple<Polyline, DBText>>(100000);

        for (int i = 0; i < 100000; i++)
        {
            Polyline polyline = new Polyline();
            polyline.AddVertexAt(0, new Point2d(i * 10, 0), 0, 0, 0);
            polyline.AddVertexAt(1, new Point2d(i * 10 + 5, 0), 0, 0, 0);
            polyline.AddVertexAt(2, new Point2d(i * 10 + 5, 5), 0, 0, 0);
            polyline.AddVertexAt(3, new Point2d(i * 10, 5), 0, 0, 0);
            polyline.Closed = true;
            polyline.ColorIndex = 2;

            DBText text = new DBText();
            text.Position = new Point3d(i * 10 + 2.5, 2.5, 0);
            text.Height = 1.0;
            text.TextString = $"ID {i}";

            groupedEntities.Add(new Tuple<Polyline, DBText>(polyline, text));
        }

        using (Transaction trans = db.TransactionManager.StartTransaction())
        {
            BlockTable bt = trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
            BlockTableRecord btr = trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

            DBDictionary groupDict = trans.GetObject(db.GroupDictionaryId, OpenMode.ForWrite) as DBDictionary;
            

             int count = 1;
            foreach (var entity in groupedEntities)
            {
                Polyline pl = entity.Item1;
                DBText txt = entity.Item2;

                string groupName = "Large_Group_Example"+count++;
                Group group = new Group(groupName, true);

                btr.AppendEntity(pl);
                trans.AddNewlyCreatedDBObject(pl, true);
                group.Append(pl.ObjectId);
                btr.AppendEntity(txt);
                trans.AddNewlyCreatedDBObject(txt, true);
                group.Append(txt.ObjectId);

                ObjectId groupId = groupDict.SetAt(groupName, group);
                trans.AddNewlyCreatedDBObject(group, true);
            }


            trans.Commit();
        }

 

 

 

 

  However, even when retrieving the polyline instances elsewhere, an eNotOpenForWrite error occurred if tr.AddNewlyCreatedDBObject was not called.

this meant that the entity had to be registered in the transaction for it to be added to the Group. So, I started thinking about how to properly retrieve the object from the transaction without using tr.AddNewlyCreatedDBObject.(Ah also I tried upgradeOpen too)

I concluded that the best way would be to use

 

 

 

 

DBObject dBObject = tr.GetObject(entity.ObjectId, OpenMode.ForRead);

 

 

 

 

  By following this approach, I first appended the object to btr.AppendEntity(), allowing it to receive an ObjectId. Then, I retrieved the object using tr.GetObject() and successfully added it to the Group with Group.Append(). 

 

 

                        ObjectIdCollection objCollection = new ObjectIdCollection();

foreach (Entity entity in groupEntities)
{
    btr.AppendEntity(entity);
    DBObject dBObject = tr.GetObject(entity.ObjectId, OpenMode.ForRead);
    //tr.AddNewlyCreatedDBObject(entity, true);
    objCollection.Add(entity.ObjectId);
}
//...
group.Append(objCollection)

 

 

As a result, I confirmed that the commit time was significantly faster. However, the object did not appear on the screen.

The fundamental and biggest issue is that the drawing file I am working with is extremely large. In smaller drawings, committing 500 objects takes less than 10 milliseconds, but in the drawing I am using, committing the same amount takes approximately 0.5 seconds.

0 Likes
Message 5 of 15

rampseeker
Advocate
Advocate

Thank you for your response.

  My main point was whether it is possible to retrieve an object using GetObject and add it to a Group without using AddNewlyCreatedDBObject within a transaction.

When the number of entities to be grouped exceeds 100,000, executing tr.Commit takes too long. So, I was looking for a way to perform grouping without calling AddNewlyCreatedDBObject.

Since calling AppendEntity alone assigns an ObjectId to the entity, I attempted to use group.Append(entity.ObjectId) without AddNewlyCreatedDBObject. However, this resulted in an eNotOpenForWrite error—even though the entity was not created within a different transaction scope.

Therefore, I would like to ask if there is any alternative solution to this issue.

 

0 Likes
Message 6 of 15

_gile
Consultant
Consultant

Adding objects to a transaction is mainly a way to ensure the newly created objects to be disposed when the transaction is disposed.

You can avoid transactions by using the "Obsolete" ObjectId.Open() method and using statements, but I'm not sure that this makes a significant improvement.

#pragma warning disable 0618
        static void LargeGroupTest(int numGroups)
        {
            var db = HostApplicationServices.WorkingDatabase;
            var groups = new List<(ObjectId, ObjectId)>();
            using (var modelSpace = (BlockTableRecord)SymbolUtilityServices.GetBlockModelSpaceId(db).Open(OpenMode.ForWrite))
            {
                for (int i = 0; i < numGroups; i++)
                {
                    using (var polyline = new Polyline())
                    using (var text = new DBText())
                    {
                        polyline.AddVertexAt(0, new Point2d(i * 10, 0), 0, 0, 0);
                        polyline.AddVertexAt(1, new Point2d(i * 10 + 5, 0), 0, 0, 0);
                        polyline.AddVertexAt(2, new Point2d(i * 10 + 5, 5), 0, 0, 0);
                        polyline.AddVertexAt(3, new Point2d(i * 10, 5), 0, 0, 0);
                        polyline.Closed = true;
                        polyline.ColorIndex = 2;
                        var plineId = modelSpace.AppendEntity(polyline);

                        text.Position = new Point3d(i * 10 + 2.5, 2.5, 0);
                        text.Height = 1.0;
                        text.TextString = $"ID {i}";
                        var textId = modelSpace.AppendEntity(text);

                        groups.Add((plineId, textId));
                    }
                }
            }
            using (var goupDict = (DBDictionary)db.GroupDictionaryId.Open(OpenMode.ForWrite))
            {
                for (int i = 0; i < groups.Count; i++)
                {
                    string groupName = $"Large_Group_Example_{i + 1}";
                    (var plineId, var textId) = groups[i];
                    using (var group = new Group(groupName, true))
                    {
                        group.Append(plineId);
                        group.Append(textId);
                        goupDict.SetAt(groupName, group);
                    }
                }
            }
        }
#pragma warning restore 0618

 

 

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 7 of 15

rampseeker
Advocate
Advocate

 

  Thank you very much for your response.

Even after modifying the statement to

BlockTableRecord btr = (BlockTableRecord)SymbolUtilityServices.GetBlockModelSpaceId(db).Open(OpenMode.ForWrite);

  as you suggested, I still encountered the Autodesk.AutoCAD.Runtime.Exception: 'eWasOpenForWrite' error at the group.Append(objCollection); statement.

However, when I retrieve the objects through a transaction using tr.GetObject() or tr.AddNewlyCreatedDBObject(), it works fine.

I have no idea why this error is occurring. Just in case, I also checked the IsWriteEnabled property of the entity, but there were no issues.

Does this mean that in order to register an object in a Group, the object must be recognized within the same transaction where the group is registered?

0 Likes
Message 8 of 15

ActivistInvestor
Mentor
Mentor

@_gile wrote:

You can avoid transactions by using the "Obsolete" ObjectId.Open() method and using statements, but I'm not sure that this makes a significant improvement.

Hi @_gile. Your test code probably isn't going to offer much improvement over the original problem (using Transactions), and that's mostly because Transactions are at their core, a list of pointers to AcDbObjects that were added/opened in the transactions. That list of AcDbObject* pointers consumes memory in the same way that this does:

 

 

var groups = new List<(ObjectId, ObjectId)>();

 

 

See my reply to the OP.

 

0 Likes
Message 9 of 15

ActivistInvestor
Mentor
Mentor

This test code shows how to avoid incremental memory usage when creating many objects.

 

All memory used by the method that creates the entities and the group is freed when the method returns. IOW, each call to that method will not result in increased memory usage (other than what is used by adding the entities to the Database)

 

/// <summary>
/// This code was designed to minimize memory overhead
/// when called many times to create many groups, each
/// with a small number of entities in each.
/// 
/// The test code creates a circle and a text entity
/// and adds both to an anonymous group.
/// </summary>
/// <param name="owner">The owner BlockTableRecord open
/// for write.</param>
/// <param name="pt">Insertion point of circle and text</param>
/// <param name="txt">The TextString for the text entity</param>
/// <param name="groupName">The name of the group (if not
/// specified the group is anonymous)</param>
/// <returns></returns>

#pragma warning disable 0618

public static ObjectId CreateGroupedEntities(BlockTableRecord owner, 
   Point3d pt, string txt, string groupName = "*")
{
   Database db = owner.Database;
   ObjectId circleId;
   ObjectId textId;
   using(var tr = new OpenCloseTransaction())
   {
      Circle circle = new Circle(pt, Vector3d.ZAxis, 1.0);
      DBText text = new DBText();
      circle.SetDatabaseDefaults(owner.Database);
      text.SetDatabaseDefaults(owner.Database);
      text.Justify = AttachmentPoint.MiddleCenter;
      text.Height = 0.5;
      text.TextString = txt;
      text.AlignmentPoint = pt;
      owner.AppendEntity(circle);
      circleId = circle.ObjectId;
      owner.AppendEntity(text);
      textId = text.ObjectId;
      tr.AddNewlyCreatedDBObject(circle, true);
      tr.AddNewlyCreatedDBObject(text, true);

      /// This is required before appending
      /// the entities to the group:
      circle.Close();
      text.Close();

      var groups = (DBDictionary)tr.GetObject(owner.Database.GroupDictionaryId, OpenMode.ForWrite);
      Group group = new Group("", true);
      groups.SetAt("*", group);
      tr.AddNewlyCreatedDBObject(group, true);
      group.Append(circleId);
      group.Append(textId);
      var result = group.ObjectId;
      tr.Commit();
      return result;
   }
}

[CommandMethod("MANYGROUPSTEST")]
public static void TestCreateGroupedEntities()
{
   PromptDoubleOptions pdo = new PromptDoubleOptions("\nHow many: ");
   pdo.AllowNegative = false;
   pdo.AllowZero = false;
   Document doc = Application.DocumentManager.MdiActiveDocument;
   Editor ed = doc.Editor;
   var pdr = ed.GetDouble(pdo);
   if(pdr.Status != PromptStatus.OK)
      return;
   long count = (int) pdr.Value;
   try
   {
      using(var tr = new OpenCloseTransaction())
      {
         var btrId = doc.Database.CurrentSpaceId;
         var btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForWrite);
         double x = 0;
         double y = 0;
         int cols = (int)(2 * Math.Sqrt(count));
         Stopwatch sw = Stopwatch.StartNew();
         for(int i = 0; i < count; i++)
         {
            CreateGroupedEntities(btr, new Point3d(x, y, 0), i.ToString());
            if((int)x > cols)
            {
               x = 0;
               y += 2;
            }
            else
            {
               x += 2.0;
            }
         }
         tr.Commit();
         sw.Stop();
         ed.WriteMessage($"\n{count} groups created in " + 
            $"{sw.Elapsed.TotalMilliseconds:00.00}ms");
      }
   }
   catch(System.Exception ex)
   {
      ed.WriteMessage(ex.ToString());
   }
}

#pragma warning enable 0618
0 Likes
Message 10 of 15

_gile
Consultant
Consultant
Accepted solution

Hi@ActivistInvestor

From the tests I did, the way you purpose is slightly slower than the one I posted. Opening the group dictionary at each iteration has certainly some overhead.

It also seems to me that creating all entities in a first time and all groups in a second one is a little faster than creating the entities and the group at each iteration (below, the method I compared to the one given above).

static void LargeGroupTest1(int numGroups)
{
    var db = HostApplicationServices.WorkingDatabase;
    using (var modelSpace = (BlockTableRecord)SymbolUtilityServices.GetBlockModelSpaceId(db).Open(OpenMode.ForWrite))
    using (var goupDict = (DBDictionary)db.GroupDictionaryId.Open(OpenMode.ForWrite))
    {
        for (int i = 0; i < numGroups; i++)
        {
            ObjectId plineId, textId;
            using (var polyline = new Polyline())
            using (var text = new DBText())
            {
                polyline.AddVertexAt(0, new Point2d(i * 10, 0), 0, 0, 0);
                polyline.AddVertexAt(1, new Point2d(i * 10 + 5, 0), 0, 0, 0);
                polyline.AddVertexAt(2, new Point2d(i * 10 + 5, 5), 0, 0, 0);
                polyline.AddVertexAt(3, new Point2d(i * 10, 5), 0, 0, 0);
                polyline.Closed = true;
                polyline.ColorIndex = 2;
                plineId = modelSpace.AppendEntity(polyline);

                text.Position = new Point3d(i * 10 + 2.5, 2.5, 0);
                text.Height = 1.0;
                text.TextString = $"ID {i}";
                textId = modelSpace.AppendEntity(text);
            }

            string groupName = $"Large_Group_Example_{i + 1}";
            using (var group = new Group(groupName, true))
            {
                group.Append(plineId);
                group.Append(textId);
                goupDict.SetAt(groupName, group);
            }
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 11 of 15

rampseeker
Advocate
Advocate

First of all, thank you for your response. However, no matter what I try, the commit time for this heavy drawing remains significantly slow, so I am currently testing the external reference (Xref) feature as a potential solution. And then I realized there was another problem.

I am currently read hundreds of fragmented Surface objects like this

 

 

private TinSurface GetSurfaceByName(string surfaceName, Transaction tr, CivilDocument civilDoc)
{
    foreach (ObjectId surfaceId in civilDoc.GetSurfaceIds())
    {
        Surface surface = tr.GetObject(surfaceId, OpenMode.ForRead) as Surface;
        if (surface != null && surface.Name.Equals(surfaceName, StringComparison.OrdinalIgnoreCase))
        {
            return surface as TinSurface; 
        }
    }
    return null; 
}

 

 

 

  I initially thought that reading data in this way would not affect the transaction at all.
In fact, when I tested this on a drawing with only 20 surfaces, everything worked fine without issues.

However, when running this on a drawing with several hundred surfaces, after performing these read operations and committing the transaction, I encountered the "eAtMaxReaders" error when trying to check the LayerTable using the LAYER command.

I assumed that the read operations would be released when the transaction scope ended, but this issue suggests otherwise.

Does reading large amounts of Surface objects hold onto transaction resources longer than expected?Does simply reading objects also affect the speed of the transaction? or, after loading TinSurface in this way, I store them in Dictionary<string, List<TinVolumeSurface>> volumeSurfaces.

 Could this approach be incorrect?

0 Likes
Message 12 of 15

ActivistInvestor
Mentor
Mentor

Hi @_gile. Are you comparing this code to the code I posted above that creates a circle and text entity? That code was really designed to work in critically-low memory conditions where avoiding the creation of large arrays might allow faster execution by avoiding memory page faults. However I can't say it might be any faster when there is enough memory available to store the the array. 

0 Likes
Message 13 of 15

kerry_w_brown
Advisor
Advisor

@rampseeker wrote:

First of all, thank you for your response. However, no matter what I try, the commit time for this heavy drawing remains significantly slow, so I am c currently testing > > >

 

@rampseeker 
Is the process actually working ??

 

For processing/doing what ??

 

What is the time that you consider " significantly slow " ??

 

How often do you run this process in your workflow ??

 

Regards,

 

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
0 Likes
Message 14 of 15

rampseeker
Advocate
Advocate


Is the process actually working ??


  No, it is still not working. When proceeding this way, an eHadMultipleReaders error occurs, and if I do not execute entity.DowngradeOpen();, an eWasOpenForWrite error occurs. I suspect that this issue is related to reference handling, but I am not sure if my assumption is correct.What I am curious about is why an error occurs stating that the object is open elsewhere when adding an ObjectId to a Group, even though I only created the Entity without opening a transaction, and then performed AppendEntity within a transaction to obtain the ObjectId.

 

 

 

List<Entity> groupEntities = CreateGroupEntities();//<-just create Entity(Region,Text,Circle...)
// Not doing transaction in Function
/*//example
	MText numberText = new MText
{
    Location = circle.Center,
    TextHeight = circle.Area / 20,
    Attachment = AttachmentPoint.MiddleCenter,
    Contents = i.ToString()
};*/
using (Transaction tr = db.TransactionManager.StartTransaction())
{
	DBDictionary groupDict = (DBDictionary)tr.GetObject(db.GroupDictionaryId, OpenMode.ForWrite);
	BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
	BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
	ObjectIdCollection objCollection = new ObjectIdCollection();
	foreach (Entity entity in result.Item2)
	{
		btr.AppendEntity(entity);
		entity.DowngradeOpen();
		objCollection.Add(entity.ObjectId);
	}
	string groupName = "Group_" + cellModel.Number;
	Group group = new Group(groupName, true);
	ObjectId groupId = groupDict.SetAt(groupName, group);
	group.Append(objCollection);
	group.SetVisibility(true);
	tr.AddNewlyCreatedDBObject(group, true);
	tr.Commit();
}
		

 

 

 

 


For processing/doing what ??


  The process involves creating Regions, assigning numbers, and adding data based on the site data provided by the user, then grouping them together and displaying them on a TIN Surface. The reason for creating Regions is to later merge multiple sites into a unified site representation.On average, the number of groups that need to be drawn is around 100,000.


What is the time that you consider " significantly slow " ??


It takes about 1 second to perform a commit for 500 objects. However, I am not sure whether this is due to reading a large amount of data in ForRead mode using tr.GetObject, or if it is specific to this drawing. The drawing I am currently working on is approximately 150MB in size.

 

How often do you run this process in your workflow ??

 


 It is executed only once. However, depending on the situation, the color of a Region with a specific text value may be changed, or the text within the group may be modified.

 

The fundamental issue is that when performing Group creation, if tr.AddNewlyCreatedDBObject(entity, true); is not executed, an eWasOpenForWrite error occurs when executing group.Append(objCollection);. Even if the boolean value in AddNewlyCreatedDBObject is set to false to prevent the transaction from managing the object, the same error still occurs.

Why does Group.Append require objects to be recognized by the transaction for it to execute properly?

0 Likes
Message 15 of 15

ActivistInvestor
Mentor
Mentor

@_gile wrote:

Hi@ActivistInvestor

From the tests I did, the way you purpose is slightly slower than the one I posted. Opening the group dictionary at each iteration has certainly some overhead.

It also seems to me that creating all entities in a first time and all groups in a second one is a little faster than creating the entities and the group at each iteration (below, the method I compared to the one given above).


Hi @_gile.

After taking a closer look at this, it appears that the single-loop approach I showed in the above post is not slower than your two-loop approach, it is slightly-faster. I was extremely puzzled about why your two-loop version appeared to run faster than a single-loop version that should have been as-fast or faster. So, I wrote two comparable versions of the test, one using your two-loop approach, and the other using the single-loop approach similar to the one I posted above. However, I tried to optimize both versions to be as fast as possible, while keeping them comparable (see code comments).

 

It was at that point that I noticed something strange. Upon opening AutoCAD and running each of the two test commands, I noticed that the test that ran first was always faster than the test that ran after it, regardless of which version was run first, and which second. That told me that the results of the test that was run second was being influenced by something that was done by the test that ran before it.

 

Mystery solved: When creating groups and adding them to the group dictionary, if a group with the name of the one being added already exists, there's more work that AutoCAD must do (e.g., open and erase the existing group).

 

So, as it turns out, the single-loop approach (MANYGROUPSTEST2) is actually slightly-faster than your two loop approach (MANYGROUPSTEST1):

 

MANYGROUPSTEST1: 25000 groups created in 1157.00ms
MANYGROUPSTEST2: 25000 groups created in 923.00ms

MANYGROUPSTEST1: 25000 groups created in 1141.00ms
MANYGROUPSTEST2: 25000 groups created in 984.00ms

MANYGROUPSTEST1: 50000 groups created in 2271.00ms
MANYGROUPSTEST2: 50000 groups created in 1817.00ms

MANYGROUPSTEST1: 100000 groups created in 4983.00ms
MANYGROUPSTEST2: 100000 groups created in 3949.00ms

 

Another thing you should make note of (and I've talked about this before), is that when using ObjectId.Open(), the code that uses the DBObject must be guarded by a try/catch, that calls Cancel() in the catch handler. If that is not done, it will most likely leave the drawing in an undefined/unknown/corrupt state.

 

See the included comments for the explanation of the above results.

 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

#pragma warning disable CS0618 // Type or member is obsolete

namespace CreateManyGroupsTests
{
   /// <summary>
   /// Evaluates the performance of two different approaches 
   /// to generating a large number of entities and groups,
   /// and explains/demonstrates why the single-loop version
   /// is slightly-faster than the 'two-loop' version.
   ///
   /// See code comments for details.
   /// </summary>
   
   public static class ManyGroupTests
   {
      static int groupNum = 0;
      static int count = 25000;

      /// <summary>
      /// Ensures that every group created has a
      /// unique name. This may not be an issue if
      /// the groups are anonymous, but AutoCAD may
      /// have to do additional work even in that 
      /// case, to find the next available anonymous 
      /// symbol name.
      /// 
      /// When one or more tests are run in the same
      /// sesson/document, and groups already exist
      /// having the same names as groups being added,
      /// there appears to be a notable performance 
      /// hit, and that's most-likely because existing 
      /// groups must be opened and erased or removed 
      /// from the groups dictionary in that case. 
      /// 
      /// That is the flaw in the testing methodology 
      /// that was being used up to this point.
      /// </summary>
      /// <returns></returns>

      static string GetUniqueGroupName()
      {
         return $"GROUP_{groupNum++}";
      }

      static Point3d GetGridPoint(Point2d cellSize, int index, int columns)
      {
         return new Point3d(
            (index % columns) * cellSize.X,
            (index / columns) * cellSize.Y, 0);
      }

      /// <summary>
      /// Performs the test operation using two loops:
      /// 
      /// The first loop creates the entities and adds
      /// them to the owner blocktablerecord. 
      /// 
      /// The second loop creates the groups and appends 
      /// the entities created in the first loop to them. 
      /// 
      /// The ObjectIds of the entities added to the group 
      /// are stored in an array of (ObjectId, ObjectId)[].
      /// 
      /// Notice also, that this code uses proper handling
      /// of DBObjects that are opened with ObjectId.Open(),
      /// or created via the new() constructor. If there is
      /// an exception during the life of the DBObject, a
      /// catch handler catches the exception and calls the
      /// Cancel() method of the DBObject, to roll-back any
      /// changes made to it. This exception handling and
      /// call to Cancel() are not optional, and necessary
      /// to ensure corrupted entities are not left behind 
      /// in the database.
      /// </summary>

      [CommandMethod("MANYGROUPSTEST1")]
      public static void ManyGroupsTest1()
      {
         PromptDoubleOptions pdo = new PromptDoubleOptions("\nHow many: ");
         pdo.AllowNegative = false;
         pdo.AllowZero = false;
         pdo.DefaultValue = (double) count;
         Document doc = Application.DocumentManager.MdiActiveDocument;
         Editor ed = doc.Editor;
         var pdr = ed.GetDouble(pdo);
         if(pdr.Status != PromptStatus.OK)
            return;
         count = (int)pdr.Value;
         int columns = (int)Math.Sqrt(count);
         Database db = doc.Database;
         var btrId = db.CurrentSpaceId;
         Point2d cellSize = new Point2d(2, 2);
         Stopwatch sw = Stopwatch.StartNew();
         try
         {
            using(var btr = (BlockTableRecord)btrId.Open(OpenMode.ForWrite))
            {
               try
               {
                  List<(ObjectId circleId, ObjectId textId)> list =
                     new List<(ObjectId, ObjectId)>();
                  // Optimization: Preallocate list storage:
                  CollectionsMarshal.SetCount(list, count);
                  // Optimization: access list via Span<T>
                  var span = CollectionsMarshal.AsSpan(list);
                  for(int i = 0; i < count; i++)
                  {
                     Point3d pt = GetGridPoint(cellSize, i, columns);
                     using(Circle circle = new Circle(pt, Vector3d.ZAxis, 1.0))
                     using(DBText text = new DBText())
                     {
                        try
                        {
                           circle.SetDatabaseDefaults(db);
                           text.SetDatabaseDefaults(db);
                           text.Justify = AttachmentPoint.MiddleCenter;
                           text.Height = 0.5;
                           text.TextString = i.ToString();
                           text.AlignmentPoint = pt;
                           btr.AppendEntity(circle);
                           btr.AppendEntity(text);
                           span[i] = (circle.ObjectId, text.ObjectId);
                        }
                        catch
                        {
                           circle?.Cancel();
                           text?.Cancel();
                           throw;
                        }
                     }
                  }
                  btr.Close();
                  using(var groups = (DBDictionary)db.GroupDictionaryId.Open(OpenMode.ForWrite))
                  {
                     try
                     {
                        for(int i = 0; i < span.Length; i++)
                        {
                           using(Group group = new Group("", true))
                           {
                              try
                              {
                                 groups.SetAt(GetUniqueGroupName(), group);
                                 group.Append(span[i].circleId);
                                 group.Append(span[i].textId);
                              }
                              catch
                              {
                                 group.Cancel();
                                 throw;
                              }
                           }
                        }
                     }
                     catch
                     {
                        groups.Cancel();
                        throw;
                     }
                  }
               }
               catch
               {
                  btr.Cancel();
                  throw;
               }
            }
            sw.Stop();
            ed.WriteMessage($"\nMANYGROUPSTEST1: {count} groups created in " +
               $"{sw.ElapsedMilliseconds}ms");
         }
         catch(System.Exception ex)
         {
            ed.WriteMessage(ex.ToString());
         }
      }

      /// <summary>
      /// Performs the same operation as the test method
      /// above, except that it uses only a single loop 
      /// that creates the entities and group, and adds 
      /// the entities to the group, and does not have to
      /// store the entity ObjectIds in an array.
      /// </summary>
      
      [CommandMethod("MANYGROUPSTEST2")]
      public static void ManyGroupsTest2()
      {
         PromptDoubleOptions pdo = new PromptDoubleOptions("\nHow many: ");
         pdo.AllowNegative = false;
         pdo.AllowZero = false;
         pdo.DefaultValue = (double) count;
         Document doc = Application.DocumentManager.MdiActiveDocument;
         Editor ed = doc.Editor;
         var pdr = ed.GetDouble(pdo);
         if(pdr.Status != PromptStatus.OK)
            return;
         count = (int)pdr.Value;
         int columns = (int)Math.Sqrt(count);
         Database db = doc.Database;
         var btrId = db.CurrentSpaceId;
         Point2d cellSize = new Point2d(2, 2);
         Point3d point = Point3d.Origin;
         try
         {
            Stopwatch sw = Stopwatch.StartNew();
            using(var owner = (BlockTableRecord)btrId.Open(OpenMode.ForWrite))
            using(var groups = (DBDictionary)db.GroupDictionaryId.Open(OpenMode.ForWrite))
            {
               try
               {
                  ObjectId circleId;
                  ObjectId textId;
                  for(int i = 0; i < count; i++)
                  {
                     using(Circle circle = new Circle(point, Vector3d.ZAxis, 1.0))
                     using(DBText text = new DBText())
                     {
                        try
                        {
                           point = GetGridPoint(cellSize, i, columns);
                           circle.SetDatabaseDefaults(db);
                           text.SetDatabaseDefaults(db);
                           text.Justify = AttachmentPoint.MiddleCenter;
                           text.Height = 0.5;
                           text.TextString = i.ToString();
                           text.AlignmentPoint = point;
                           owner.AppendEntity(circle);
                           owner.AppendEntity(text);
                           circleId = circle.ObjectId;
                           textId = text.ObjectId;
                        }
                        catch
                        {
                           circle?.Cancel();
                           text?.Cancel();
                           throw;
                        }
                     }
                     using(Group group = new Group("", true))
                     {
                        try
                        {
                           group.Append(circleId);
                           group.Append(textId);
                           groups.SetAt(GetUniqueGroupName(), group);
                        }
                        catch(System.Exception)
                        {
                           group.Cancel();
                           throw;
                        }
                     }
                  }
               }
               catch
               {
                  owner.Cancel();
                  groups.Cancel();
                  throw;
               }
            }
            sw.Stop();
            ed.WriteMessage($"\nMANYGROUPSTEST2: {count} groups created in " +
               $"{sw.ElapsedMilliseconds}ms");
         }
         catch(System.Exception ex)
         {
            ed.WriteMessage(ex.ToString());
         }
      }
   }
}

 

 

 

0 Likes