OpenMaya.MEventMessageとScriptJobの実行タイミングは違う

この記事は[Takumi Akashiro ひとり Advent Calendar 2020](https://qiita.com/advent-calendar/2020/takumi_akashiro "ひとり Advent Calendar 2020")の22日目の記事です。

## 始めに

MEventMessage、ScriptJobどちらもEventが発火したら登録したコールバック関数を実行させる仕組みですが、
実はMEventMessageとScriptJobの実行タイミングは違います。

コマンドリファレンスをしっかりと読んだことがある方は、何故かそうなる理解できると思います。


## まずはどちらも試してみる

今回は分かりやすいSelectionChanged Eventにフックさせていきます

```python:sample_scriptJob.py
#! python2
# encoding: utf-8
from maya import cmds


def print_func(text):
    print(text)


if __name__ == '__main__':
    if 'scriptjob_id' in globals() and cmds.scriptJob(ex=scriptjob_id):
        cmds.scriptJob(kill=scriptjob_id)
    scriptjob_id = cmds.scriptJob(event=["SelectionChanged", "print_func('SelectionChanged: ScriptJob')"])

```

![22_01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/4ebe2c4f-afc6-8bcf-1751-fe73c00855e9.gif)

```python:sample_m_event_message.py
#! python2
# encoding: utf-8
from maya.api import OpenMaya


def print_func(text):
    print(text)


if __name__ == '__main__':

    if 'm_event_message_cbk_id' in globals():  # コールバックIDが生きてるか確認する手段が思いつかなったのでtry-exceptで誤魔化す。
        try:
            OpenMaya.MEventMessage.removeCallback(m_event_message_cbk_id)
        except RuntimeError, UnicodeDecodeError:  # UnicodeDecodeErrorは日本語環境用
            pass
    m_event_message_cbk_id = OpenMaya.MEventMessage.addEventCallback("SelectionChanged", print_func, 'SelectionChanged: MEventMessage')

```

![22_02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/734288a7-4813-a4d8-2618-863969bd3d39.gif)

ScriptJobもMEventMessageも同じく動いている'ように'見えますね！


## MEventMessageとScriptJob、その違い
ではScriptEditorで以下を実行してみます。

```python
#! python2
# encoding: utf-8
from maya import cmds

for x in range(3):
    cmds.select(cmds.ls(sl=True))
```

**ScriptJob**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/b6a7b813-e8d4-ee8e-237e-354a88e21a0e.png)

**OpenMaya.MEventMessage**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/d5fc1a30-76f4-64e0-a05e-fe3db4a6e107.png)


なんということでしょう！
ScriptJobの方は1回しか実行されてないですが、MEventMessageは3回とも実行されててますね。

## どうしてそうなるのか

これにはScriptjobの実行タイミングに秘密があります。
公式リファレンスを読んでみましょう。

> スクリプト ジョブは、インタラクティブなアプリケーションでイベント ループに結び付けられています。
> **このジョブはアイドル イベント中に実行されます。**
> このことは、スクリプト ジョブがバッチ アプリケーションに存在しないことを意味しています。scriptJob コマンドは、バッチ モードでは何も行いません。
> [Command Reference: scriptJob - Maya ヘルプ](http://help.autodesk.com/view/MAYAUL/2018/JPN/?contextId=COMMANDSPYTHON-SCRIPTJOB) より抜粋

というわけでScriptJobは処理がいったん終わって、アイドル状態にならないと動かないのです。

なのでScriptJobとMEventMessageを併用した状態で、さっきのスクリプトにprint文を足して動かすと……

```python
#! python2
# encoding: utf-8
from maya import cmds

print('-'*10 + '\nStart')

for x in range(3):
    print('Selection_before: %s' % x)
    cmds.select(cmds.ls(sl=True))
    print('Selection_after: %s' % x)
    if x != 2:
        print('-'*10)

print('-'*10 + '\nEnd')

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/78371dea-c767-36cd-4cec-3b71ea3b7b3f.png)


処理直後にMEventMessageが走り、処理が完全に終わったあと、
Mayaがアイドルになった段階でScriptJobがやっと実行されるわけです。

## 締め

違いが分かると、処理回数をできる限り減らすためにScriptJobを使ったり、
厳密に監視するためにMEventMessageを使うなど、使い方が分けられると思います。

Eventにフックさせる系は便利ですが、引き換えに操作レスポンスを悪くする可能性があるので、
ここらへんは意識できるといいですね！