Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Is it safe to store Schema and Field references in an add-in static variable of a static class?

9 REPLIES 9
Reply
Message 1 of 10
stefanome
731 Views, 9 Replies

Is it safe to store Schema and Field references in an add-in static variable of a static class?

stefanome
Collaborator
Collaborator

I have tried using the following code while switching documents and it seems to work.

All the methods in SchemaManager call GetSchema() to make sure the 4 static variables have a reference to the Schema and its fields, then use the static fields inside the code.

 

I tested it working while switching between two documents, and everything seems to be working just fine.

I was expecting some kind of crash, because (I think) I am caching the static fields on Doc1 and using them in Doc2.

 

Are the static fields always reset when I start a new command?

Is the scope of the static fields somehow reset when switching documents?

Are schema and field unique across documents?

Am I doing something really wrong and my quick tests don't show the problem?

public static class SchemaManager
{
private static Schema _schema;
private static Field _numbers, _strings, _points;

private static void GetSchema()
{
if (_schema != null)
return;

var schemaGuid = new Guid("603564a1-cc29-4545-a24b-5e72e0818aae");
_schema = Schema.Lookup(schemaGuid);
if (_schema == null)
{
var schemaBuilder = new SchemaBuilder(schemaGuid);
schemaBuilder.SetSchemaName("IntelliClad");
schemaBuilder.SetReadAccessLevel(AccessLevel.Public);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Vendor);
schemaBuilder.SetVendorId("UniverseCorp");

schemaBuilder.AddMapField("Numbers", typeof(string), typeof(double)).SetSpec(SpecTypeId.Length);
schemaBuilder.AddMapField("Strings", typeof(string), typeof(string));
schemaBuilder.AddMapField("Points", typeof(string), typeof(XYZ)).SetSpec(SpecTypeId.Length);

_schema = schemaBuilder.Finish();
}

_numbers = _schema.GetField("Numbers");
_strings = _schema.GetField("Strings");
_points = _schema.GetField("Points");
}

 

0 Likes

Is it safe to store Schema and Field references in an add-in static variable of a static class?

I have tried using the following code while switching documents and it seems to work.

All the methods in SchemaManager call GetSchema() to make sure the 4 static variables have a reference to the Schema and its fields, then use the static fields inside the code.

 

I tested it working while switching between two documents, and everything seems to be working just fine.

I was expecting some kind of crash, because (I think) I am caching the static fields on Doc1 and using them in Doc2.

 

Are the static fields always reset when I start a new command?

Is the scope of the static fields somehow reset when switching documents?

Are schema and field unique across documents?

Am I doing something really wrong and my quick tests don't show the problem?

public static class SchemaManager
{
private static Schema _schema;
private static Field _numbers, _strings, _points;

private static void GetSchema()
{
if (_schema != null)
return;

var schemaGuid = new Guid("603564a1-cc29-4545-a24b-5e72e0818aae");
_schema = Schema.Lookup(schemaGuid);
if (_schema == null)
{
var schemaBuilder = new SchemaBuilder(schemaGuid);
schemaBuilder.SetSchemaName("IntelliClad");
schemaBuilder.SetReadAccessLevel(AccessLevel.Public);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Vendor);
schemaBuilder.SetVendorId("UniverseCorp");

schemaBuilder.AddMapField("Numbers", typeof(string), typeof(double)).SetSpec(SpecTypeId.Length);
schemaBuilder.AddMapField("Strings", typeof(string), typeof(string));
schemaBuilder.AddMapField("Points", typeof(string), typeof(XYZ)).SetSpec(SpecTypeId.Length);

_schema = schemaBuilder.Finish();
}

_numbers = _schema.GetField("Numbers");
_strings = _schema.GetField("Strings");
_points = _schema.GetField("Points");
}

 

9 REPLIES 9
Message 2 of 10
joshua.lumley
in reply to: stefanome

joshua.lumley
Advocate
Advocate

Using 'static' in your classes and methods isn't any different from using non-static from Revits perspective....'static' is an avoidance of object orientated programming that just means you can't make instances (objects) from the class.

 

The big rule, as I understand it, with schemas is that you can't change them after they have been used in a document. Or put another way, if you change a schema (e.g. by adding or removing a new mapfield) after it has been used - then you must always give it a new GUID.

Using 'static' in your classes and methods isn't any different from using non-static from Revits perspective....'static' is an avoidance of object orientated programming that just means you can't make instances (objects) from the class.

 

The big rule, as I understand it, with schemas is that you can't change them after they have been used in a document. Or put another way, if you change a schema (e.g. by adding or removing a new mapfield) after it has been used - then you must always give it a new GUID.

Message 3 of 10
stefanome
in reply to: joshua.lumley

stefanome
Collaborator
Collaborator

Thanks for the answer, but my question was about the safety of using the same static reference to a Schema or a Field across multiple documents... assuming that that's what's actually happening.

 

My static class is a lazy class, that is it looks for or creates the schema and stores it in a static field the first time a method is called, then it keeps reusing the same value for the life of... I don't know what. 

 

I think that the lifecycle of the static fields on a Revit add-in (ie the lifecycle of the add-in) ends when Revit shuts down, not when the active document changes or something else happens. Which means that the a Schema stored in a static fields is going to be used with all the documents open during one session.

 

And I think that the same Schema and a Field fields can be safely used with multiple documents because the static Schema.Lookup method doesn't ask for any document argument.

 

But before I rely on this, I would like the blessing of some expert.

0 Likes

Thanks for the answer, but my question was about the safety of using the same static reference to a Schema or a Field across multiple documents... assuming that that's what's actually happening.

 

My static class is a lazy class, that is it looks for or creates the schema and stores it in a static field the first time a method is called, then it keeps reusing the same value for the life of... I don't know what. 

 

I think that the lifecycle of the static fields on a Revit add-in (ie the lifecycle of the add-in) ends when Revit shuts down, not when the active document changes or something else happens. Which means that the a Schema stored in a static fields is going to be used with all the documents open during one session.

 

And I think that the same Schema and a Field fields can be safely used with multiple documents because the static Schema.Lookup method doesn't ask for any document argument.

 

But before I rely on this, I would like the blessing of some expert.

Message 4 of 10
joshua.lumley
in reply to: stefanome

joshua.lumley
Advocate
Advocate

Did you mean an 'unsave' use is when you go to use the schema and it is not there?

When using schema's I always use Schema.Lookup to check if it's there...and then if it is not there created it. 

Example:

Schema schema_FurnLocations = Schema.Lookup(new Guid("330a1ede-d77b-4350-963d-3505f7ae5e23"));
if (schema_FurnLocations == null) schema_FurnLocations = Schema_FurnLocations.createSchema_FurnLocations();


        public static Schema createSchema_FurnLocations()
        {
            Guid myGUID = new Guid("330a1ede-d77b-4350-963d-3505f7ae5e23");
            SchemaBuilder mySchemaBuilder = new SchemaBuilder(myGUID);
            mySchemaBuilder.SetSchemaName("FurnLocations");

            FieldBuilder mapField_Child = mySchemaBuilder.AddMapField("FurnLocations", typeof(ElementId), typeof(XYZ));
            mapField_Child.SetUnitType(UnitType.UT_Length);

            FieldBuilder mapField_Child_Angle = mySchemaBuilder.AddMapField("FurnLocations_Angle", typeof(ElementId), typeof(double));
            mapField_Child_Angle.SetUnitType(UnitType.UT_Length);
            //IList<int> list = new List<int>() { 111, 222, 333 };


            FieldBuilder mapField_Child_Pattern = mySchemaBuilder.AddMapField("FurnLocations_Pattern", typeof(ElementId), typeof(ElementId));
            FieldBuilder mapField_Child_Red = mySchemaBuilder.AddMapField("FurnLocations_ColorRed", typeof(ElementId), typeof(int));
            FieldBuilder mapField_Child_Green = mySchemaBuilder.AddMapField("FurnLocations_ColorGreen", typeof(ElementId), typeof(int));
            FieldBuilder mapField_Child_Blue = mySchemaBuilder.AddMapField("FurnLocations_ColorBlue", typeof(ElementId), typeof(int));

            return mySchemaBuilder.Finish();
        }
0 Likes

Did you mean an 'unsave' use is when you go to use the schema and it is not there?

When using schema's I always use Schema.Lookup to check if it's there...and then if it is not there created it. 

Example:

Schema schema_FurnLocations = Schema.Lookup(new Guid("330a1ede-d77b-4350-963d-3505f7ae5e23"));
if (schema_FurnLocations == null) schema_FurnLocations = Schema_FurnLocations.createSchema_FurnLocations();


        public static Schema createSchema_FurnLocations()
        {
            Guid myGUID = new Guid("330a1ede-d77b-4350-963d-3505f7ae5e23");
            SchemaBuilder mySchemaBuilder = new SchemaBuilder(myGUID);
            mySchemaBuilder.SetSchemaName("FurnLocations");

            FieldBuilder mapField_Child = mySchemaBuilder.AddMapField("FurnLocations", typeof(ElementId), typeof(XYZ));
            mapField_Child.SetUnitType(UnitType.UT_Length);

            FieldBuilder mapField_Child_Angle = mySchemaBuilder.AddMapField("FurnLocations_Angle", typeof(ElementId), typeof(double));
            mapField_Child_Angle.SetUnitType(UnitType.UT_Length);
            //IList<int> list = new List<int>() { 111, 222, 333 };


            FieldBuilder mapField_Child_Pattern = mySchemaBuilder.AddMapField("FurnLocations_Pattern", typeof(ElementId), typeof(ElementId));
            FieldBuilder mapField_Child_Red = mySchemaBuilder.AddMapField("FurnLocations_ColorRed", typeof(ElementId), typeof(int));
            FieldBuilder mapField_Child_Green = mySchemaBuilder.AddMapField("FurnLocations_ColorGreen", typeof(ElementId), typeof(int));
            FieldBuilder mapField_Child_Blue = mySchemaBuilder.AddMapField("FurnLocations_ColorBlue", typeof(ElementId), typeof(int));

            return mySchemaBuilder.Finish();
        }
Message 5 of 10
stefanome
in reply to: joshua.lumley

stefanome
Collaborator
Collaborator

That's what I do, as I show in my snipped in the original post.

 

My question is whether it is safe to store in a static field whatever Schema I found.

0 Likes

That's what I do, as I show in my snipped in the original post.

 

My question is whether it is safe to store in a static field whatever Schema I found.

Message 6 of 10
joshua.lumley
in reply to: stefanome

joshua.lumley
Advocate
Advocate

Sorry can you clarify why your using static fields at all, what is their purpose? Below is a quote from Revit API Docs.

 

 

Schemas are stored in the memory of the running instance of Revit and may be retrieved with the Lookup method. When a document containing Entities of a Schema is saved, the Schema is saved with the document too. Opening that document reintroduces the Schema into memory.

 

0 Likes

Sorry can you clarify why your using static fields at all, what is their purpose? Below is a quote from Revit API Docs.

 

 

Schemas are stored in the memory of the running instance of Revit and may be retrieved with the Lookup method. When a document containing Entities of a Schema is saved, the Schema is saved with the document too. Opening that document reintroduces the Schema into memory.

 

Message 7 of 10
stefanome
in reply to: joshua.lumley

stefanome
Collaborator
Collaborator

When I have a class that uses a schema and its fields, the first time the class is used it will use the Schema.Lookup and schema.GetField to find or create the schema. Then I store the schema and the schema fields in static members of the class.

 

Every time another member of the class is used, it will not waste time looking up for the schema and all its fields, because they all are available in the static fields.

 

I always try to implement this kind of lazy loading for both static and non static classes, when possible and when useful.

 

It's useful if storing the values in static members will avoid looking for them thousands of times in a loop. I know it's useful, because I do have loops calling that static class members.

 

It's possible when the lifetime of the stored object is compatible with the lifetime of the static variable, that is I don't want to preserve a Revit stale object in my class static field. I don't know if it's possible, because I don't know if those static references can become stale.

 

The documentation snippet that you mention says "Opening that document reintroduces the Schema into memory". Does it mean that the reintroduction will make my existing references stale?

 

Thanks for adding this snippet of the documentation. I did think about testing it with two documents open, but I didn't think about testing with Doc1, then with Doc2, then saving and closing them both, then reopening them again, then testing it again.

0 Likes

When I have a class that uses a schema and its fields, the first time the class is used it will use the Schema.Lookup and schema.GetField to find or create the schema. Then I store the schema and the schema fields in static members of the class.

 

Every time another member of the class is used, it will not waste time looking up for the schema and all its fields, because they all are available in the static fields.

 

I always try to implement this kind of lazy loading for both static and non static classes, when possible and when useful.

 

It's useful if storing the values in static members will avoid looking for them thousands of times in a loop. I know it's useful, because I do have loops calling that static class members.

 

It's possible when the lifetime of the stored object is compatible with the lifetime of the static variable, that is I don't want to preserve a Revit stale object in my class static field. I don't know if it's possible, because I don't know if those static references can become stale.

 

The documentation snippet that you mention says "Opening that document reintroduces the Schema into memory". Does it mean that the reintroduction will make my existing references stale?

 

Thanks for adding this snippet of the documentation. I did think about testing it with two documents open, but I didn't think about testing with Doc1, then with Doc2, then saving and closing them both, then reopening them again, then testing it again.

Message 8 of 10
RPTHOMAS108
in reply to: stefanome

RPTHOMAS108
Mentor
Mentor

Probably a good test would be to create an ElementID field then set it to an element and then delete the element in Revit. It should then be set to Invalid when you then inspect it, since extensible storage is meant to track that possibility.

 

If you review the static information after deleting the element and it isn't invalid then that's a problem of old information.

 

There is probably further complications to investigate regarding schema entities associated with families and schema entities in linked documents.

 

The schema conflict issue (where schema must be fixed with id) kind of points to it being a situation where two instances (entities) of the same schema information exist in memory at the same time (must have consistent structure for a given id). So I'm sceptical the static approach wouldn't lead to old information at some point. We are also getting information from unmanaged memory which doesn't have the same lifecycle as managed. So the get it when you need it rather than store it in managed for use later, would be generally the safer approach.

 

Below are some historic links which may aid your understanding:

 

https://thebuildingcoder.typepad.com/files/cp4451_tammik_estorage.pdf

https://thebuildingcoder.typepad.com/files/cp6760-l_tammik_estorage.pdf

 

 

Probably a good test would be to create an ElementID field then set it to an element and then delete the element in Revit. It should then be set to Invalid when you then inspect it, since extensible storage is meant to track that possibility.

 

If you review the static information after deleting the element and it isn't invalid then that's a problem of old information.

 

There is probably further complications to investigate regarding schema entities associated with families and schema entities in linked documents.

 

The schema conflict issue (where schema must be fixed with id) kind of points to it being a situation where two instances (entities) of the same schema information exist in memory at the same time (must have consistent structure for a given id). So I'm sceptical the static approach wouldn't lead to old information at some point. We are also getting information from unmanaged memory which doesn't have the same lifecycle as managed. So the get it when you need it rather than store it in managed for use later, would be generally the safer approach.

 

Below are some historic links which may aid your understanding:

 

https://thebuildingcoder.typepad.com/files/cp4451_tammik_estorage.pdf

https://thebuildingcoder.typepad.com/files/cp6760-l_tammik_estorage.pdf

 

 

Message 9 of 10
joshua.lumley
in reply to: RPTHOMAS108

joshua.lumley
Advocate
Advocate

Just did a benchmark on Schema.Lookup & schema.GetField the CPU time was <1ms as I expected (this was after the schema was created in session).

If the reason behind storing is schema's in static members is optimization, the time saved will be imperceptible.

0 Likes

Just did a benchmark on Schema.Lookup & schema.GetField the CPU time was <1ms as I expected (this was after the schema was created in session).

If the reason behind storing is schema's in static members is optimization, the time saved will be imperceptible.

Message 10 of 10
stefanome
in reply to: joshua.lumley

stefanome
Collaborator
Collaborator

I agree that both Lookup and GetField are fast, and that I shouldn't bother optimizing performance until I know that I need to do it. It's just second nature for me: when I create a new class I always use some lazy loading where possible.

 

Here are two more considerations:

  1. If I get 1 Schema and 10 Fields and it takes 5 ms, and I do it in a loop 1000 times, my lazy loading will save 5 seconds, which is a very good thing.
  2. Usually, after getting the schema and its fields, I will do something with them (getting or setting values for example), and chances are that whatever I do with them is slower than getting them. So my lazy loading may save 5 seconds on a 50 second job, which is still a good thing, but not as good.

When the add-in is ready I will do some profiling and see what's worth optimizing.

I agree that both Lookup and GetField are fast, and that I shouldn't bother optimizing performance until I know that I need to do it. It's just second nature for me: when I create a new class I always use some lazy loading where possible.

 

Here are two more considerations:

  1. If I get 1 Schema and 10 Fields and it takes 5 ms, and I do it in a loop 1000 times, my lazy loading will save 5 seconds, which is a very good thing.
  2. Usually, after getting the schema and its fields, I will do something with them (getting or setting values for example), and chances are that whatever I do with them is slower than getting them. So my lazy loading may save 5 seconds on a 50 second job, which is still a good thing, but not as good.

When the add-in is ready I will do some profiling and see what's worth optimizing.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report