Validating Attribute Associations and Reference Item Values

With the addition of Association Rules that introduce dependencies beyond simple Persistent Object type, I wanted to describe what these new dependancies are and how they are enforced.

Exclusion and Prerequisites

These concepts were introduced in Supporting Exclusion and Required Constraints in Attribute Resources. Basically an attribute (A) may not be associated to a Persistent Object if:

  • The object has an attribute already associated with it that excludes A (note that this rule is symmetric : if A excludes B then B also excludes A)
  • The object does not have an attribute associated with it that is a prerequisite for A to also be associated with the object (Note that this rule cannot be symmetric)

The Attribute::associate(…) and Attribute::disassociate(…) methods enforces the above rules so neither in code or in the GUI can these constraints be violated.

Association Rules with Property Constraints

The ability to include constraints based on Property values have recently been supported in Association Rules. For example you can now state attributes of type Foo can be associated to model faces that have a string property “Type” whose value is “Bar”.

The Attribute::associate(…) methods can enforce these constraints. Note that in this case the Attribute::disassociate(…) can not invalidate this constraint (mainly because the prerequisite mechanism does not depend on property constraints.

Property Modification

Everything works fine assuming that the properties associated with Persistent Objects are constant. This is not the case if an object’s properties are modified. Consider the above example where we have an model face F1 with a property Type=“Bar” and an Attribute A1 of type Foo. Based on Foo’s association requirement, F1 can be associated with A1. Now if sometime afterwards F1 loses its Type property (or perhaps it is changed to “Wall”), it should no longer be associated to A1, but A1 does not know of the property change.

Possible Solutions

Using Resource and Operation Management with Observers

In this case (theoretically), an observer could listen for property changes and if one occurs, it could look at all of the attribute resources and determine if any associations have become invalid and remove them . There are a couple pitfalls with this approach:

  1. Since the attribute resource may be changed as the result of an observer being fired, the operation that caused the property to change would not correctly report that both the persistent object and the attribute were modified. This would be cause issues with a GUI-based application like ModelBuilder
  2. It would be a pain to debug (most observer-based interactions can be hard to trace)
  3. Would be difficult to do in a multi-threaded/parallel environment - consider the how the setProperty operator works. It’s prerequisite is that the resource (in this case the model resource (MR) owning F1) needs to be modifiable. It doesn’t stipulate anything about the attribute resource (AR) owning A1. So consider the scenario where MR is not being used by any currently running operation but AR is. So the property operation begins to run and suddenly rules into a problem when the Observer tries to change AR. The only recourse would be to have the property operation launch an attribute validate operation that would execute sometime later.

Using Resource and Operation Management without Observers

In this case the setProperty operation would directly do the validation itself by examining the resources in the resource manager and looking for attribute resources that are associated with the current resource being modified (in this case MR).

The benefits of this method are

  1. Simpler to debug - no observations going on
  2. Ability for the operation to also return the modified attribute resources
  3. Ability to provide an option to not change a property if it would cause invalidations.

Pitfalls with this approach:

  • Possible multi-threading issues as in the previous case though it might be possible for the operation to request all of the resources currently depending on the property to be modifiable.

Not Using Any Management

We could provide a mechanism that would associate a list of resources (via weak pointers) that has dependancies on a specific property and when that property changes those resources are then told to update.
The benefits of this method are

  1. Ability for the operation to also return the modified attribute resources
  2. Ability to provide an option to not change a property if it would cause invalidations.
  3. Does not depend on any management.

Pitfalls with this approach:

  • Possible multi-threading issues as in the previous case though it might be possible for the operation to request all of the resources currently depending on the property to be modifiable.
  • Would introduce yet another observation like system

External Validation Method/Operation

In this case the association are left invalid and there is a method that would reexamine the attribute resources. You could provide two options:

  1. One that would check the entire resource
  2. One that would take in the object whose property was changed and do a more focused validation.

The main setback with this approach are:

  1. There would be no possibility to test to see if removing a property would cause an invalidatey.
  2. Things are left in an invalid state and the user/developer has to remember to revalidate explicitly.

Reference Items

Currently reference items do not have exclusion or prerequisite constraints though I’m the process of implementing the ability to prevent 2 different reference items from being assigned to the same persistent object. We do have the property modification issue since you can use properties in the reference item’s assignment rule.

In addition, there are new “constraints” that have been introduced in the Qt widgets for reference items such as a reference item should only be assigned objects that are currently associated with the item’s owning attribute. Note that this “rule” is only available in the GUI system and would not be validated using anything in SMTK Core.

This option seems simplest, it’s reversible, traceable, and handling validation within the operation is a clean solution.

As for the pitfall, what use case(s) would illustrate the pain points?

@dcthomp Perhaps smtk::operation::Operation::configure() could be used to populate the SetProperty's parameters with all of the resources it is interested in modifying? That way, smtk::operation::Operation::operate() would properly lock the resources it needs to modify. What do you think?

Let’s say we want to set a “type” property on some component C in resource A. We create a SetProperty operation which will write-lock resource A for us. Now inside the operation, we iterate over resources in the resource manager and discover that resource B has links to resource A. That means it is possible resource B needs to perform an update in response to SetProperty. But we don’t have a lock on resource B. If some other thread has a lock on resource B we could deadlock trying to acquire the lock (it might be trying to the same for resource A). Another nasty case would be that the operation which reconciles resource B with the property change on C requires a lock on a third resource D. The SetProperty operation would need to be very clever about locking resources to work in all cases (or would need to blindly lock every managed resource).

One way around this is to have SetProperty queue a follow-on operation to deal with the ramifications of property changes on C. However, this does potentially leave the application in an inconsistent state. Also, if SetProperty is called within some other operation, how will that one know about the follow-on operation?

1 Like

Yes, that’s what I meant in my post just after yours by “be very clever about locking resources.”

Another concern is that it is possible for operations other than SetProperty to set properties. Whatever we do to make SetProperty deal with the consequences of property changes, we should make it available to other operations that may wish to set properties. Haocheng’s work on RGG is a good example of this use case: he sets properties on auxiliary geometry entities indicating whether they are pins, ducts, etc. as the user makes edits in his custom view.

I see, very well explained.

Should PropertyModified be an observable action?

I thought you hated the observer pattern. :slight_smile:

What would hold the observers? The resource or operation manager? (Note: This is a trick question.)

It’s not my favorite pattern, but it is what we have decided to use.

Properties are on resources. So, the resource manager would have to observe them (as always, if the resource is managed).

Besides, all we’d be doing is adding another state to the existing resource observer. That is a lot less obtrusive than introducing another observer system.

Well, you didn’t fall into the trap I set for you… but I feel strangely compelled to explain. Properties (whether on components or resources) are held by resources, but they should only be modified during operations. The trap is that currently attributes are modified outside of operations.

Still, making another set of observers just for properties (or adding property modifications to the existing resource observers) seems like a lot of overhead. Now all the descriptive phrase stuff will have to have the same code called in response to operations (that might create/expunge/modify components with properties) and in response to resource changes (property modification alone). As problematic as operation observers for attributes might be, I don’t see a win proportional to the added complexity of a separate set of observers for property changes.

Not this one!

If the API for accessing (and therefore modifying) properties is on the resource, then this cannot (and should not) be an enforced convention.

Technically, any call that changes a resource’s property should be triggering resource observers with a MODIFIED flag anyway. Indeed, all of the non-const API on smtk::resource::Resource invoke an observer. Creating a different channel for property modification would give observers more information about what they should do (and what they can ignore).