MPPS 3.0 How to use listeners

  • 13
  • 59

In http://forum.jetbrains.com/thread/Meta-Programming-System-814 it states that MPS 3.0 has a number of new listener classes. Is there documentation, or are there examples on how to use these listener classes. I am interested in which listen classes/interfaces are available and how I can add them as a listener to a model or node.
Nobody is using this? I have sevral places in my language where I need this now and still don't know how to approach it.

I am assuming I need to use an addListener(...) method defined on an SModel. First of all, there are two SModel classes, one in jetbrains.mps.model, and one in the openapi. I am only able to get access to an SModel from the openapi. I can add SModelAccessListener using the addAccessListener() method. But I need to add a SModelChangeListener which cannot be added.
I then found a subclass of SModel called EditableSModel that allows one to add change listeners using addChangeListener(...). Bingo I thought!
But, after I went through the complete openapi concerning models, repository and modules, I found no way to get an EditableSModel. Does anyone know how to get a model as an EditableSModel?

You are right - EditableSModel and SModelChansgeListener is the way to listen model events in new API. In the same package you can find two more listeners - SModelAccessListener and SModelListener.

Now about listeners. Most probably, two of them will be enough for a non-core developer:
SModelListener - to track model state (loading/unloading, tracking loading problems etc)
SModelChangeListener - to track changes to individual nodes in the model (different change operations)
The Access listener is about dependencies tracking, which is used in MPS core aspects (editor rebuild, typesystem, make etc)

Models. In OpenAPI (introduced in 3.0), modules consist of models, models consist of nodes. There are no division to SModelDescriptor and SModel, as was before. The SModel class is a non-API class implementing a collection of nodes. SModel interface is a model as it is shown in API (actually, for now the real representer is mostly SModelDescriptor). The classes have the same name for the purpose of migration. Further we'll talk only about the SModel interface.
Now, there are two kind of models: the one you can only load (like java-stub models) and the one you can change. For those you can change, there's the EditableSModel interface* using it, you can attach a change listener. For those you can't change, there's only SModelListener that can be attached.
So, the answer for the question "where to obtain EditableSModel" is: when you have a model, just check whether it's instance of EditableSModel, cast and attach the listener. For any model that is not instance of EditableSModel, you don't need the change listener to be attached as its methods will never be called.

Sorry for long delay, we have missed the first post and I saw the last one only today. Just in case, bump it if you don't see the answer for a reasonable time ) - MPS team will get an email then.

Regards,
Mihail

Mihail,

thanks for the above explanation. I did some tests and it does seem to work as you explain. This will help quite a lot.

I have one question remaining. Right now, I have an intention to add a listener to a model. I would like to add a listener to several models immediately when MPS is started. Is there a hook to do this at startup somewhere?

Jos

Is there a hook to do this at startup somewhere?

Depends on why do you need this.
You can do a model listening, which will guarantee no model is changed without calling your listener, but this can be done only with a core plugin. I think, if you describe your use-case, maybe I could advice a simpler way of doing this.

I might use a model listener, but I still need a hook to register this model listener at startup of MPS, just as I needed one to register the change listener.

Ok, then here's the hardcore solution:
1) create a new plugin for MPS
2) create an ApplicationComponent, register it in the IDE through plugin.xml
3) add parameter in its constructor, with type MPSModuleRepository
4) now you can add an SRepositoryContentAdapter listener to the repository or manage multiple SModelChangeListeners manually

Steps 1-3 are needed to run a code before any model or module is registered

Hmm, I find quite interesting behavior of the listener. If I start out with an expression A + B in the baseLanguage, and type a plus resulting in A + B +, I get the following add and remove callbacks in my listener
Remove B
Add B
Remove B
Add B
Remove B
Add B

This is of course due to the right transformations in the baseLanguage and the restructuring of the tree, but this is something to be very much aware of because the B is actually never removed. Any behavior attached to the Remove B callback needs to take this into account. I am still thinking on how to deal with this.

Jos

This is how it works for now. Events describe exact changes made to model, not a result of a batch change. Why is it a problem handling them?
I could suppose that maybe you need to batch the events yourself (by-command) using the ModelAccess.addCommandListener(), which is not API but still. As right-transform should be executed as one command, in your case, you could get all the events for the whole right-transform action batched, and filter out those you don't need.

It is a problem in my specific use case which works as follows.

For each node added of a certain type I add an extra node with additional properties that can be changed by the user. If a node is removed, I remove the corresponding node with the extra properties. When a node is removed and added again because of the transformations I can add the node with extra properties again, but have lost their values and they will all get the default values.

In other use cases it might not be a problem, or it might even be needed to get all the exact changes to a model.

Jos

I would recommend to use a map (node->propNode) for deleted nodes. When node is deleted, put the pair to the map, when node is added, lookup the map and restore the propNode. The map should be cleared on the end of each command (use CommandListener).

Maybe it would also be appropriate to store the propNode as a child. But this depends on the purpose why you use two nodes instead of one.

BTW, aren't you writing a view-model, for something like graph/diagram editor?

Models. In OpenAPI (introduced in 3.0), modules consist of models, models consist of nodes. There are no division to SModelDescriptor and SModel, as was before. The SModel class is a non-API class implementing a collection of nodes. SModel interface is a model as it is shown in API (actually, for now the real representer is mostly SModelDescriptor). The classes have the same name for the purpose of migration. Further we'll talk only about the SModel interface.
Now, there are two kind of models: the one you can only load (like java-stub models) and the one you can change. For those you can change, there's the EditableSModel interface* using it, you can attach a change listener. For those you can't change, there's only SModelListener that can be attached.
GuL