ページ 11

【解決済】競合させないプラグインの基本とは

Posted: 2022年2月26日(土) 15:13
by ブラインド
こんにちは。
自分用のプラグインですが
せっかくなので競合しないようにしたいと思い

無名関数だとか、関数を退避させるとかの話を聞いたので
やってみようとしましたが良くわかりませんでした。

根本的な勘違いをしているはずなので、それを指摘していただければ。

コード: 全て選択

(() => {
Var _SBS_DisplayDamage = Window_BattleLog.prototype.displayDamage
Window_BattleLog.prototype.displayDamage = function(target) {
    if (target.result().missed) {
        this.displayMiss(target);
    } else if (target.result().evaded) {
        this.displayEvasion(target);
    } else {
        this.displayHpDamage(target);
      //  this.displayMpDamage(target);
        this.displayTpDamage(target);
    }
};
})();

Re: 競合させないプラグインの基本とは

Posted: 2022年2月26日(土) 17:15
by くろうど
こんにちは。
私の分かる範囲で概念を図にしました。

下の図のプラグインBが自分のプラグインだとすると、
他の人のプラグインAの処理を含んで、
元の「関数2」を上書きすることができます。
func_061.png
退避した関数には退避した処理が全て含まれますので、
自分のやりたい処理と見比べて、
処理の順序や重複などを気にする必要があります。

さて、具体的にどう書くかというと……。

この「退避」の部分は以下のように書きます。

コード: 全て選択

const _Window_BattleLog_displayDamage = Window_BattleLog.prototype.displayDamage;
変数名は任意ですが、先頭に「_」アンダースコアを付ける習慣らしいです。
ちなみに、「const」でなくても構いませんが、「Var」ではなく小文字で「var」です。

次に、関数内で「退避した関数」を呼び出す必要があります。

コード: 全て選択

_Window_BattleLog_displayDamage.apply(this, arguments);
とするのが楽です。

「arguments」の中には今回であれば、引数の「target」が入っています。

もし、引数の値を変更する場合は、callを使う方が変えた事が分かりやすいと思います。
callの場合は引数を書く必要があります。

コード: 全て選択

const changedTarget = target + 1;
_Window_BattleLog_displayDamage.call(this, changedTarget);
まずはここまでで、よろしくお願いします。

Re: 競合させないプラグインの基本とは

Posted: 2022年2月26日(土) 19:00
by ムノクラ
ブラインド さんが書きました:こんにちは。
自分用のプラグインですが
せっかくなので競合しないようにしたいと思い

無名関数だとか、関数を退避させるとかの話を聞いたので
やってみようとしましたが良くわかりませんでした。

根本的な勘違いをしているはずなので、それを指摘していただければ。

コード: 全て選択

(() => {
Var _SBS_DisplayDamage = Window_BattleLog.prototype.displayDamage
Window_BattleLog.prototype.displayDamage = function(target) {
    if (target.result().missed) {
        this.displayMiss(target);
    } else if (target.result().evaded) {
        this.displayEvasion(target);
    } else {
        this.displayHpDamage(target);
      //  this.displayMpDamage(target);
        this.displayTpDamage(target);
    }
};
})();
くろうど氏(自分の認識ではプロの技術者)の解説があるので、蛇足になりますが、具体例を含めて足しになればと思い投稿します。
まずは、ド素人の戯言であることを理解した上でお読みいただければ幸いです。


1.ツクールの関数の引数(ひきすう)は
Window_BattleLog.prototype.displayDamage = function (target) {
の行の「target」を指します。


2.くろうど氏の言う「退避」をプログラマは一般的に「フック処理」と呼ぶそうです。
なにかの検索に役に立つかも知れないワードなので、お伝えしておきます。
下記には、実際にプラグインを作りながら、フック処理を組み込む過程が書かれています。
https://fungamemake.com/archives/12338


3.具体的な対策をしたコードを順に出します。

その1

コード: 全て選択

(() => {
    var _Window_BattleLog_displayDamage = Window_BattleLog.prototype.displayDamage;
    Window_BattleLog.prototype.displayDamage = function (target) {
        if (target.result().missed) {
            _Window_BattleLog_displayDamage .call(this, target);
        } else if (target.result().evaded) {
            _Window_BattleLog_displayDamage .call(this, target);
        } else {
            this.displayHpDamage(target);
            //  this.displayMpDamage(target);
            this.displayTpDamage(target);
        }
    };
})();
その2

コード: 全て選択

(() => {
    var _Window_BattleLog_displayDamage = Window_BattleLog.prototype.displayDamage;
    Window_BattleLog.prototype.displayDamage = function (target) {
        if (target.result().missed || target.result().evaded) {
            _Window_BattleLog_displayDamage .call(this, target);
        } else if (target.result().evaded) {
            this.displayHpDamage(target);
            //  this.displayMpDamage(target);
            this.displayTpDamage(target);
        }
    };
})();
4.自分はフック処理にcallを勧めます。
多くの技術者は
apply(this, arguments)
を使用するようですが、
return
で終わる関数で使うとエラーになった経験があるからです。

https://raw.githubusercontent.com/munok ... ParamMV.js
このプラグインでは
Game_BattlerBase.prototype.paramMax
const _Game_BattlerBase_paramMin
を上書きしていますが、フック処理を return に入れる必要があります。
この場合には
apply(this, arguments)
は直接使用できません。
(多分、別変数を作って代入してから、それを return させれば可能だと思いますが…)

Re: 競合させないプラグインの基本とは

Posted: 2022年2月26日(土) 20:38
by Plasma Dark
無名関数だとか、関数を退避させるとかの話を聞いたので
やってみようとしましたが良くわかりませんでした。
プラグインを作る時には、無名関数の中にコードを書いて即時実行し、スコープを区切るのが一般的になっています。
これは、グローバルスコープに必要以上にシンボルを定義しないことを目的としています。
あらゆるプラグインがグローバルスコープにシンボルを定義してしまうと、容易に名前が衝突します。

IIFEと呼ばれ、よく知られているテクニックです。
https://developer.mozilla.org/en-US/docs/Glossary/IIFE

退避については概ねくらうどさんの書き込み通りですが、補足します。
「arguments」の中には今回であれば、引数の「target」が入っています。
argumentsは配列風のオブジェクトで、targetを含んでいる、と表現するのが正確です。
https://developer.mozilla.org/ja/docs/W ... /arguments
https://developer.mozilla.org/ja/docs/W ... tion/apply

「退避」をプログラマは一般的に「フック処理」と呼ぶそうです。
プログラムに対して利用者が別のプログラムで処理を追加できる仕組みを「フック」と呼び、処理を追加することを「フックする」と表現します。
フック処理という言葉は口語的で、意味としてはフックした処理のことか、あるいはフックすることそのものを表すのだろうとわかりますが、プログラマが一般的にそう呼んでいるかというと疑問です。
多くの技術者は
apply(this, arguments)
を使用するようですが、
return
で終わる関数で使うとエラーになった経験があるからです。
これは何か別の要因でエラーになったのではないでしょうか。
applyもcallも、返値は変わりません。
https://developer.mozilla.org/ja/docs/W ... tion/apply
https://developer.mozilla.org/ja/docs/W ... ction/call

applyを使用する人が多いのは記述量を減らせるためだと思われます。
jsは仕様としてargumentsという便利なオブジェクトを提供してくれているので、引数をいちいちすべて書くよりもこれだけで済ませてしまうほうが記述量が減り、結果として引数の順序の間違いやtypoなどのつまらない不具合に悩まされずに済む、という思想なのでしょう。
引数として渡すシンボルを明示したい場合のみcallを使うべきとする思想もありますが、一方で文脈依存で呼び出すメソッドを変えるとかえってバグのもとになるため(可変長引数などの特殊なケースを除いて)callに統一すべきとする思想もあります。
(私は後者の思想でコードを書いています)

いくらかコードを書いてみて、好みのほうを使えば良いと思います。

Re: 競合させないプラグインの基本とは

Posted: 2022年2月26日(土) 23:54
by 名無し蛙
あまりたくさんの人が言及しても仕方が無いですけど気になったので。

例題、
Window_BattleLog.prototype.displayDamageからMPダメージ表示処理をコメントアウトする
という改変には処理の退避を使用する必要性はないと思います。
競合回避とはプラグインを組む上での考え方の話であり
メソッドの退避はそれを実現する為のテクニックの一つに過ぎません。
切り分けされてないメソッドの真ん中の処理に干渉したい場合は不向きですし
今回のように既存の処理を削る改変をしたい場合にはあまり出番が無いです。
競合問題が発生する可能性はどう組んでも0には出来ないし
特に自分用なら問題が発生した時に柔軟に対処する保守性も重要だと思います。

見返した時の読み易さ、今後更なる改変を考えるなら別にこのままでも良いですし

コード: 全て選択

Window_BattleLog.prototype.displayDamage = function(target) {
    if (target.result().missed) {
        this.displayMiss(target);
    } else if (target.result().evaded) {
        this.displayEvasion(target);
    } else {
        this.displayHpDamage(target);
      //  this.displayMpDamage(target);
        this.displayTpDamage(target);
    }
};
最小限の干渉を重視するならdisplayMpDamageの中身を空っぽ(またはコメントアウト)にしても良いです。
displayDamage以外で使用するメソッドでもないので。

コード: 全て選択

Window_BattleLog.prototype.displayMpDamage = function(target) {
};

Re: 競合させないプラグインの基本とは

Posted: 2022年2月27日(日) 13:26
by ブラインド
多くの皆様、返信いただきありがとうございます。
くろうど さんが書きました:
下の図のプラグインBが自分のプラグインだとすると、
他の人のプラグインAの処理を含んで、
元の「関数2」を上書きすることができます。

退避した関数には退避した処理が全て含まれますので、
自分のやりたい処理と見比べて、
処理の順序や重複などを気にする必要があります。
図までいただきありがとうございます。上乗せして拡張できるわけですね。
だからこそ順序が大事なのがなんとなくわかった気がします。
変数名は任意ですが、先頭に「_」アンダースコアを付ける習慣らしいです。
ちなみに、「const」でなくても構いませんが、「Var」ではなく小文字で「var」です。
何故か大文字になっていました(絶句)
ムノクラ さんが書きました: くろうど氏の言う「退避」をプログラマは一般的に「フック処理」と呼ぶそうです。
Plasma Dark さんが書きました:プログラムに対して利用者が別のプログラムで処理を追加できる仕組みを「フック」と呼び、処理を追加することを「フックする」と表現します。
フック処理という言葉は口語的で、意味としてはフックした処理のことか、あるいはフックすることそのものを表すのだろうとわかりますが、プログラマが一般的にそう呼んでいるかというと疑問です。
フック処理自体、聞いたことはありましたが理解を諦めていました。
退避のことだと思えば、今なら理解が追いつくかもしれません
プラグインを作る時には、無名関数の中にコードを書いて即時実行し、スコープを区切るのが一般的になっています。
これは、グローバルスコープに必要以上にシンボルを定義しないことを目的としています。
あらゆるプラグインがグローバルスコープにシンボルを定義してしまうと、容易に名前が衝突します。

IIFEと呼ばれ、よく知られているテクニックです。
IIFE、これは初耳でしたね。また一つ勉強になりました
argumentsについては、配列だと思っておけば大丈夫でしょう。
名無し蛙 さんが書きました:Window_BattleLog.prototype.displayDamageからMPダメージ表示処理をコメントアウトする
という改変には処理の退避を使用する必要性はないと思います。
競合回避とはプラグインを組む上での考え方の話であり
メソッドの退避はそれを実現する為のテクニックの一つに過ぎません。
切り分けされてないメソッドの真ん中の処理に干渉したい場合は不向きですし
今回のように既存の処理を削る改変をしたい場合にはあまり出番が無いです。
競合問題が発生する可能性はどう組んでも0には出来ないし
特に自分用なら問題が発生した時に柔軟に対処する保守性も重要だと思います。
実際のところ、これだけの処理で退避させる必要性は感じられませんでした。
将来的には覚えたほうがプラグイン同士の競合の原因を早期発見したり
出来るかと思ったので、試したみた次第ですね。