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");
}
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");
}
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.
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.
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.
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();
}
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();
}
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.
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.
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.
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.
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.
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.
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
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.
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.
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:
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:
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.