LoginSignup
8

More than 5 years have passed since last update.

WatsonのUnity-SDKから消えたConfiguration Editorを調査し、今後の方針を考えてみる(1/2)

Last updated at Posted at 2018-04-23

IBM の developerWorks サイトの記事 Watson×Unity!初心者でもできる、VR 空間で Unity ちゃんとおしゃべりアプリ! を現時点(2018年4月15日)の最新環境(バージョン)で試してみました、ところ… 初っ端から Configuration Editor が無くて困ってしまいました。

その顛末は 試してみる続・試してみる にまとめて、書き直したサンプルプログラムを公開しました。

とりあえず落ち着きましたので、そもそもどうしてこうなったのか?どう対応するのが正解なのか?を2回にわけてちゃんと考えたいとおもいます。後編 も公開しました。

消えたConfiguration Editor

元の dW記事 は公開が昨年の9月と古いもので Unity 5.6.2f1 に、Watson Unity-SDK v0.13.0 を使用していて、以下のメニューがあります。

image.png

私の環境は Unity 2017.4.0f1 に、Watson Unity-SDK v2.2.1 を使用していて、以下のメニューしかありません。

私の環境の場合

今回のdW記事でよく使用しているConfiguration Editorが無いので、いきなり最初で詰まってしまいますね。どこに行ってしまったのでしょう?

以前の投稿 にまとめましたが、便利ツールであるConfiguration Editorを使わず、利用するコード側でCredentials管理をするのがv1.0.0以降のお作法であり、正解なのかな?が私の結論です。

でもConfiguration Editorって、実際には何をしてくれていたのでしょうか?そして今後はどう実装したら良いのでしょうか?

Unityエディター拡張から探る

エディター拡張入門 サイトの情報が役に立ちます。学びつつ、探っていきましょう。

第1章 エディター拡張で使用するフォルダー によりますと、Editorフォルダーが大事なようなので、Unity-SDK v0.13.0 のフォルダ内を眺めると… 確かにありますね。

image.png

第8章 MenuItem あたりなど参考に、まずは ConfigEditor.cs を眺めてみましょう。192行目にそれっぽいコードがあります。

ConfigEditor.cs
[MenuItem("Watson/Configuration Editor", false, 0)]
private static void EditConfig()
{
    GetWindow<ConfigEditor>().Show();
}

うん、やはり。この ConfigEditor.cs ファイルで定義されている ConfigEditor クラスが、消えたConfiguration Editorの本体のようです。この中身をざっと眺めてみました。

ConfigurationEditorクラスの役割

ConfigurationEditorクラスで GUI を作成する、OnGUI クラスの主要部分のコードを以下にまとめます。222行目あたりから。

まずは上部にあるサービスの一覧表示の部分のGUI作成コード。詳細は削っているので注意。

ConfigEditor.cs
private void OnGUI()
{
    Config cfg = Config.Instance;
    GUILayout.Label(m_WatsonIcon);  // 左上のWatsonアイコン

    if (GUILayout.Button("Register for Watson Services"))  // 最初のボタン
        Application.OpenURL(BLUEMIX_REGISTRATION);

    foreach (var setup in SERVICE_SETUP)
    {
        Config.CredentialInfo info = cfg.FindCredentials(setup.ServiceID);
        bool bValid = GetIsValid(setup);

        GUILayout.BeginHorizontal();  // 【サービスの行表示】 開始

        // 各サービスのステータスのアイコン表示
        if (m_ServiceStatus.ContainsKey(setup.ServiceID))
        {
            if (m_ServiceStatus[setup.ServiceID])
                GUILayout.Label(m_StatusUp, GUILayout.Width(20));
             else
                GUILayout.Label(m_StatusDown, GUILayout.Width(20));
        }
        else
            GUILayout.Label(m_StatusUnknown, GUILayout.Width(20));

        // 各サービスの説明テキスト
        GUILayout.Label(
            string.Format(
                "Service {0} {1}.",
                setup.ServiceName,
                bValid ? "CONFIGURED" : "NOT CONFIGURED"
            ),
            labelStyle
        );

        // 各サービスのアクションボタン
        if (GUILayout.Button("Configure", GUILayout.Width(100)))
            Application.OpenURL(setup.URL);
        if (bValid && GUILayout.Button("Clear", GUILayout.Width(100)))
            cfg.Credentials.Remove(info);

        GUILayout.EndHorizontal();  // 【サービスの行表示】 終了
    }

各サービスのCredential情報、つまり「資格情報」を読み込んでいます。ポイントとしては

  • info = cfg.FindCredentials(setup.ServiceID) で資格情報を読み込む
  • bValid = GetIsValid(setup) でサービスがきちんと設定されているか確認する

あたりで、ここらへんを追っていけば資格情報の格納先がわかりそうです。

ちなみに、ステータスのアイコン表示に使用されているリソースは以下です。

image.png

以下、引き続き OnGUI 関数の後半をみていきます。

ConfigEditor.cs
    GUILayout.Label("PASTE CREDENTIALS BELOW:");

    // Credential情報を貼り付けるテキストエリア
    m_PastedCredentials = EditorGUILayout.TextArea(m_PastedCredentials);

    // Credential情報を反映するApplyボタン
    GUI.SetNextControlName("Apply");
    if (GUILayout.Button("Apply Credentials"))
    {
        Config.CredentialInfo newInfo = new Config.CredentialInfo();
        if (newInfo.ParseJSON(m_PastedCredentials))
        {
            foreach (var setup in SERVICE_SETUP)
            {
                if (newInfo.m_URL.EndsWith(setup.ServiceAPI))
                {
                    newInfo.m_ServiceID = setup.ServiceID;
                    // 過去にCredential情報があれば削除する処理
                    cfg.Credentials.Add(newInfo);
                }
            }
        }
        // 成功もしくは失敗のダイアログを表示
        SaveConfig();
    }
}

こちらは「資格情報」の書き込みの処理ですが、重要なのはたぶん以下の3ポイントだと思われます。

  • newInfo = new Config.CredentialInfo() で新しい資格情報を生成
  • newInfo.ParseJSON 関数でJSON形式の資格情報を取り込み
  • cfg.Credentials.Add(newInfo) で新しい資格情報を登録

資格情報に関しては、以下のオブジェクトが担っているのがわかります。

ConfigEditor.cs
Config cfg = Config.Instance;

このオブジェクトについて、もう少し見ていきましょう。

Configクラスから探る

Config.cs ですが、Editor ではなく Utilities フォルダの中にありました。

まず読み出しのための FindCredentials関数を探してみると、217行目にあります。

Config.cs
public CredentialInfo FindCredentials(string serviceID)
{
  foreach (var info in m_Credentials)
    if (info.m_ServiceID == serviceID)
      return info;
  return null;
}

うん、単に探しているだけの関数ですね。そして肝心の資格情報の格納場所である m_Credentials は以下で設定されていました。

Config.cs
[fsProperty]
private List<CredentialInfo> m_Credentials = new List<CredentialInfo>();
public List<CredentialInfo> Credentials {
    get { return m_Credentials; }
    set { m_Credentials = value; }
}

うん、m_Credentials は単なるCredentialInfoのリストのようですね。すると上にある[fsProperty]属性が重要な気がします。

で、この属性がどこで定義してあるのかわからず悩んだのですが、結論から言ってこれ FullSerializer という独立したライブラリでした。コードは ThirdParty フォルダの中にあります。

というわけで、ここでわかったのは

  • Config オブジェクトに資格情報が保存されている
  • Config オブジェクトはFullSerializerというライブラリを用いてSerialize可能にされている

ですね。Configクラスはデータを抽象化したクラス(モデル)であり、アプリケーションに依存したロジックは無いことがわかります。

Serializeされた情報がどこに保存されるのか、Configクラスを眺めただけではわからない、ということになります。残念ですが、ひとつ前に戻って探し直しましょう。

再びConfigurationEditorクラス

そういえばConfiguration Editorの下のほうに「Save」というボタンがありますね。さきほどの OnGUI関数の最後のほう、その部分のコードを見てみましょう。

ConfigEditor.cs
    if (GUILayout.Button("Save"))
        SaveConfig();

おや?この SaveConfig って先ほどもありましたね。めっちゃ怪しい名前じゃないですか?そのコードを見てみると…

ConfigEditor.cs
private static void SaveConfig()
{
    if (!Directory.Exists(Application.streamingAssetsPath))
        Directory.CreateDirectory(Application.streamingAssetsPath);

    File.WriteAllText(
        Application.streamingAssetsPath + "/Config.json",
        Config.Instance.SaveConfig()
    );
    RESTConnector.FlushConnectors();
}

なるほど File.WriteAllText関数がありますので、思いっきりテキスト情報をファイルに書き込んでいますね。ココでしたか!そして使用されている streamingAssetsPath なんですが、Unityマニュアルにある ストリーミングアセット ですよね。嫌な予感がします…

Unity プロジェクトにおける StreamingAssets と呼ばれるフォルダーに配置したファイルはビルド先のプラットフォームの、特定のフォルダーにそのまま何も変換されない状態で保持されます。

ノーガード戦法キター!新しいバージョンでサポートされていないハズですよ、平文でIDやパスワード保持しちゃってた、ってことですよね?

第3章 データの保存 にある 3.2 EditorUserSettings.Set/GetConfigValue であれば、まだ暗号化されたものを…

SpeechToText APIから逆に探る

なんとなく見えてきた気がしますが… 念のため逆から、信頼情報を利用するWatson API側も見ておきましょう。

試してみる で最初に確認した SpeechToText API ですが、元のサンプルコードは

SampleSpeechToText.cs
private SpeechToText m_SpeechToText = new SpeechToText();

と引数無しでSpeechToTextインスタンスを生成していたわけです。ということは、その内部でConfig Editorが保存した「信頼情報」を参照しているハズ! Services/SpeechToText フォルダにある SpeechToText.cs ファイルを見てコードを確認してみましょう。

以下、音声認識を実行する関数の先頭部分です。

SpeechToText.cs
public bool Recognize(AudioClip clip, OnRecognize callback)
{
  if (clip == null)
    throw new ArgumentNullException("clip");
  if (callback == null)
    throw new ArgumentNullException("callback");

  RESTConnector connector = RESTConnector.GetConnector(SERVICE_ID, "/v1/recognize");
  if (connector == null)
    return false;

RESTConnector.GetConnector のところが怪しい感じです。v2.2.1のほうは同じ部分が以下のようになっており、渡した Credentials をちゃんと利用しています。

SpeechToText.cs
    RESTConnector connector = RESTConnector.GetConnector(Credentials, "/v1/recognize");
    if (connector == null)
        return false;

やはりRESTConnectorが怪しい!というわけで、Connection フォルダにある RESTConnector.cs コードを追ってみましょう。

RESTConnector.cs
public static RESTConnector GetConnector(string serviceID, string function, bool useCache = true)
{
    RESTConnector connector = null;

    string connectorID = serviceID + function;
    if (useCache && sm_Connectors.TryGetValue(connectorID, out connector))
      return connector;

    Config cfg = Config.Instance;
    Config.CredentialInfo cred = cfg.FindCredentials(serviceID);
    if (cred == null)
    {
        Log.Error("Config", "Failed to find credentials for service {0}.", serviceID);
        return null;
    }

RESTConnectorクラスのGetConnectorメソッドの中にありましたね、FindCredentials(serviceID) と信頼情報をゲットしている部分が。こんな下のサービスレベルで信頼情報をやりとりしていたとは… で、この信頼情報を格納した Config オブジェクトは Instance メソッドにより

Config.cs
public static Config Instance {
   get { return Singleton<Config>.Instance; }
}

とシングルトンで得られるわけで。なるほどねぇ。そしてConfigクラスのコンストラクターが、例のConfig.jsonファイルを読みだしていることが確認できました。

Config.cs
public Config()
{
    LoadConfig();
}

public void LoadConfig()
{
    if (!Directory.Exists(Application.streamingAssetsPath))
        Directory.CreateDirectory(Application.streamingAssetsPath);
    LoadConfig(System.IO.File.ReadAllText(Application.streamingAssetsPath + Constants.Path.CONFIG_FILE));

}
public bool LoadConfig(string json)
{
    // 実際の処理は省略
}

いやぁ、なかなか長い確認の旅でしたw

まとめ

Watson Unity-SDK にあったConfiguration Editorですが、信頼情報をConfiguration Editorに入力すると、内部でConfigオブジェクトに情報をまとめ、UnityのストリーミングアセットとしてConfig.jsonというファイルに保存してくれていた。

SpeechToTextなどのWatson APIのほう、サービス利用側は信頼情報など気にせずサービス用のオブジェクトを生成する。すると内部でRESTConnectorクラスがConfigオブジェクトを呼び出し、サービスの種類に応じた信頼情報を自動的に利用してくれていた。

以上、非常に便利な仕組みだが、たぶん欠点が2つあって…

  • Unityのストリーミングアセットに(たぶん)平文としてIDやパスワードが保存されている
  • サービスの種類から信頼情報を自動的にセットすることしかできない(明示的に指定できない)ので、サービスごとに1つの設定しか利用できない

という流れで、この仕組みはお蔵入りしたのではないか?と想像します。

というわけで

Watson Unity-SDK にあったConfiguration Editorですが、ストリーミングアセット に Config.json という大胆な名前でシリアライズ化した信頼情報を保存していた、というなかなか豪快な事実がわかりました。

正式っぽい v1.0.0 以降から Configuration Editor が消えた理由が、なんとなく想像できる気がしますね… そしてこのままの復活は難しいであろうことが予想できます。

あたらしい Unity-SDK がそのあたりを明確にガイドしてくれていれば良いのですが、いまのところは無さそうです。自分たちで考えなければいけないかもしれません。

というわけで対応策を考えたいのですが… 長くなりましたし、いったん落ち着きましたので、本日はこれで。もうすぐ GW なので、じっくり考えてまとめたいとおもいます!

GWにまとめました> 後編

ではでは。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8