コードが1回だけ実行されるようにするにはどうすればよいですか?


18

コードをトリガーする状況が複数回発生する可能性がある場合でも、一度だけ実行したいコードがあります。

たとえば、ユーザーがマウスをクリックしたときに、次のものをクリックします。

void Update() {
    if(mousebuttonpressed) {
        ClickTheThing(); // I only want this to happen on the first click,
                         // then never again.
    }
}

ただし、このコードでは、マウスをクリックするたびにクリックされます。一度だけそれを実現するにはどうすればよいですか?


7
「ゲーム固有のプログラミングの問題」に該当するとは思わないので、実際にこの種の面白いことを見つけます。しかし、私はそのルールが本当に好きではありませんでした。
ClassicThunder

3
@ClassicThunderうん、それは確かにプログラミングの一般的な側面です。必要に応じて閉会に投票してください。質問をもっと投稿しましたので、これに似た質問をするときに人々を指すようにします。そのために開いたり閉じたりできます。
マイケルハウス

回答:


41

ブールフラグを使用します。

示されている例では、コードを次のように変更します。

//a boolean flag that lets us "remember" if this thing has happened already
bool thatThingHappened = false;

void Update() {
    if(!thatThingHappened && mousebuttonpressed) {
        //if that thing hasn't happened yet and the mouse is pressed
        thatThingHappened = true;
        ClickTheThing();
    }
}

さらに、アクションを繰り返したいが、アクションの頻度を制限する場合(つまり、各アクション間の最小時間)。同様のアプローチを使用しますが、一定時間後にフラグをリセットします。それに関するより多くのアイデアについては、ここ私の答えを参照しください。


1
あなたの質問に対する明白な答え:-)あなたまたは他の誰かが何かを知っているなら、私は代替的なアプローチを見たいと思います。このためにグローバルブール値を使用することは、常に私にとって臭いです。
-Evorlor

1
@Evorlor誰にも明らかではない:)。私も代替ソリューションに興味があります。それほど明白ではない何かを学ぶことができるかもしれません!
マイケルハウス

2
@Evorlorの代わりに、デリゲート(関数ポインター)を使用して、実行するアクションを変更できます。例えばonStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }void doSomething() { doStuff(); delegate = funcDoNothing; }しかし最終的にすべてのオプションはあなたが設定/設定解除する何らかのフラグを持ちます...そしてこの場合のデリゲートは他に何もありません(おそらく3つ以上のオプションがある場合を除いて?)
ウォンドラ

2
@wondraええ、それは方法です。それを追加します。この質問に関する可能な解決策のリストを作成することもできます。
マイケルハウス

1
@JamesSnellマルチスレッドである場合に可能性が高くなります。しかし、それでも良い習慣です。
マイケルハウス

21

boolフラグでは不十分な場合、またはvoid Update()メソッド内のコードの読みやすさを改善したい場合は、デリゲート(関数ポインター)の使用を検討できます。

public class InputController 
{
  //declare delegate type:
  //<accessbility> delegate <return type> <name> ( <parameter1>, <paramteter2>, ...)
  public delegate void ClickAction();

  //declare a variable of that type
  public ClickAction ClickTheThing { get; set; }

  void onStart()
  {
    //assign it the method you wish to execute on first click
    ClickTheThing = doStuff;
  }

  void Update() {
    if(mousebuttonpressed) {
        //you simply call the delegate here, the currently assigned function will be executed
        ClickTheThing();
     }
  }

  private void doStuff()
  {
    //some logic here
    ClickTheThing = doNothing; //and set the delegate to other(empty) funtion
  }

  //or do something else
  private void doNothing(){}
}

単純な「1回実行」デリゲートは過剰すぎるため、代わりにboolフラグを使用することをお勧めします。
ただし、より複雑な機能が必要な場合は、デリゲートを選択することをお勧めします。たとえば、1つ目のクリックで1つ、2つ目のクリックで1つ、3つ目のクリックで1つだけ実行できるさまざまなアクションをチェーンで実行する場合は、次のようにします。

func1() 
{
  //do logic 1 here
  someDelegate = func2;
}

func2() 
{
  //do logic 2 here
  someDelegate = func3;
}
//etc...

数十の異なるフラグでコードを悩ます代わりに。
* 残りのコードの保守性が低下する代償


予想通り、プロファイリングを実行しました。

----------------------------------------
|     Method     |  Unity   |    C++   |
| -------------------------------------|
| positive flag  |  21 ms   |   6 ms   |
| negative flag  |  5 ms    |   7 ms   |
| delegate       |  25 ms   |   14 ms  |
----------------------------------------

最初のテストはUnity 5.1.2で実行されSystem.Diagnostics.Stopwatch、32ビットのビルドプロジェクトで測定されました(デザイナーではありません!)。/ Oxフラグを使用して32ビットリリースモードでコンパイルされたVisual Studio 2015(v140)のもう1つ。両方のテストはIntel i5-4670K CPU @ 3.4GHzで実行され、実装ごとに10,000,000回の反復が行われました。コード:

//positive flag
if(flag){
    doClick();
    flag = false;
}
//negative flag
if(!flag){ }
else {
    doClick();
    flag = false;
}
//delegate
action();

結論: Unityコンパイラーは関数呼び出しを最適化する際に良い仕事をしますが、肯定的なフラグとデリゲート(それぞれ21ミリ秒と25ミリ秒)の両方にほぼ同じ結果を与えますが、ブランチの予測ミスや関数呼び出しは依然として非常に高価です(注:デリゲートはキャッシュと見なされるべきです)このテストでは)。
興味深いことに、Unityコンパイラーは、連続して9900万の予測ミスがある場合にブランチを最適化するのに十分なほどスマートではないため、テストを手動で否定すると、パフォーマンスが向上し、5ミリ秒の最良の結果が得られます。C ++バージョンでは、条件を否定するためのパフォーマンスは向上しませんが、関数呼び出しの全体的なオーバーヘッドは大幅に低くなります。
最も重要なこと:違いはほとんど無関係です 実際のシナリオに


4
私の知る限り、これはパフォーマンスの観点からも優れています。関数が既に命令キャッシュにあると仮定して、no-op関数の呼び出しは、特にそのチェックが他の条件の下にネストされている場合、フラグのチェックよりもはるかに高速です。
エンジニア

2
Navinは正しいと思います。ブールフラグをチェックするよりもパフォーマンスが低下する可能性があります-JITが本当に賢く、関数全体を文字通り何も置き換えない限り。しかし、結果をプロファイルしない限り、確実に知ることはできません。
ウォンドラ

2
うーん、この答えはSWエンジニアの進化を思い出させます。冗談はさておき、このパフォーマンスの向上が絶対に必要な(そして確実な)場合は、それを試してください。そうでない場合は、別の回答で提案されているように、KISSを維持し、ブールフラグを使用することをお勧めします。ただし、ここに示されている代替案の+1です!
ムカホ

1
可能な限り条件を避けてください。私は、それがあなた自身のネイティブコード、JITコンパイラ、またはインタプリタであろうと、マシンレベルで何が起こるかについて話している。L1キャッシュヒットはレジスタヒットとほぼ同じで、わずかに遅い(1または2サイクル)可能性がありますが、ブランチの予測ミスはあまり頻繁に発生しませんが、平均してあまりにも多く、10〜20サイクルのコストがかかります。Jalfの答えを参照してください:stackoverflow.com/questions/289405/…そしてこれ:stackoverflow.com/questions/11227809/…–
エンジニア

1
さらに、Byte56の答えのこれらの条件は、プログラムの存続期間中、更新ごとに呼び出す必要があり、ネストされているか、ネストされた条件が含まれている可能性があり、事態をさらに悪化させます。関数ポインタ/デリゲートは、行く方法です。
エンジニア

3

完全を期すために

(実際にこれを行うことはお勧めしませんが、実際にはのようなものを書くのはかなり簡単ですがif(!already_called)、それを行うのは「正しい」でしょう。)

当然のことながら、C ++ 11標準は、関数を1回呼び出すというささいな問題をイディオム化し、非常に明確にしました。

#include <mutex>

void Update() {
if(mousebuttonpressed) {
    static std::once_flag f;
    std::call_once(f, ClickTheThing);
}

}

確かに、標準ソリューションは、スレッドが存在する場合の些細なソリューションよりも多少優れています。これは、常に正確に1つの呼び出しが発生し、決して異なることがないことを保証するためです。

ただし、通常、マルチスレッドのイベントループを実行して、同時に複数のマウスのボタンを押すことはないため、スレッドセーフティは少し不必要です。

実際には、ローカルブール値のスレッドセーフ初期化(C ++ 11ではスレッドセーフであることが保証されている)に加えて、標準バージョンは呼び出しの前または呼び出しの前にアトミックなtest_set操作も実行する必要があることを意味します関数。

それでも、明示的であることを好むのであれば、解決策があります。

編集:
ハァッ。私はここに座って数分間そのコードを見つめていたので、私はそれをお勧めしようとしています。
実際、それは最初に見えるほど愚かではなく、実際にあなたの意図を非常に明確かつ明確に伝えています...他のソリューションを含む他のソリューションよりも間違いなくより構造的で読みやすいですif


2

一部の提案は、アーキテクチャによって異なります。

clickThisThing()を関数ポインター/バリアント/ DLL / so /などとして作成し、オブジェクト/コンポーネント/モジュール/などがインスタンス化されるときに必要な関数に初期化されることを確認します。

マウスボタンが押されるたびにハンドラーでclickThisThing()関数ポインターを呼び出し、すぐにポインターを別のNOP(操作なし)関数ポインターに置き換えます。

フラグや追加のロジックは必要ありません。後で呼び出すと、毎回呼び出すだけで置き換えられます。NOPなので、NOPは何もせず、NOPに置き換えられます。

または、フラグとロジックを使用して呼び出しをスキップできます。

または、呼び出し後にUpdate()関数を切断して、外の世界がそれを忘れて二度と呼び出さないようにすることもできます。

または、clickThisThing()自体がこれらの概念のいずれかを使用して、有用な作業を1回だけ応答するようにします。このように、ベースラインclickThisThingOnce()オブジェクト/コンポーネント/モジュールを使用して、この動作が必要な場所でインスタンス化できます。また、アップデーターは、至る所で特別なロジックを必要としません。


2

関数ポインターまたはデリゲートを使用します。

関数ポインタを保持している変数は、変更が必要になるまで明示的に調べる必要はありません。上記のロジックを実行する必要がなくなったら、空の/ no-op関数へのrefに置き換えます。起動後の最初の数フレームでのみそのフラグが必要だったとしても、プログラムの全ライフサイクルにわたってすべてのフレームを条件付きでチェックすることと比較してください!- 汚れており、非効率的。(ただし、予測に関するBorealのポイントは認められています。)

これらがよく構成するネストされた条件は、フレームごとに1つの条件をチェックするよりもコストが高くなります。その場合、分岐予測は魔法をかけられなくなるからです。これは、数十サイクルの定期的な遅延を意味します-パイプラインは卓越してストールします。ネストは、このブールフラグの上または下で発生します。複雑なゲームループがすぐに発生する可能性があることを読者に思い出させる必要はほとんどありません。

このため、関数ポインタが存在します-それらを使用してください!


1
私たちは同じ方針に沿って考えています。最も効率的な方法は、作業が完了したら、容赦なくそれを呼び出し続ける人からUpdate()を切断することです。これは、Qtスタイルのシグナル+スロットセットアップで非常に一般的です。OPはランタイム環境を指定しなかったので、特定の最適化に関しては私たちは苦労しています。
パトリックヒューズ

1

javascriptやluaなどの一部のスクリプト言語では、関数参照のテストを簡単に行うことができます。でLuaのlove2d):

function ClickTheThing()
      .......
end
....

local ctt = ClickTheThing  --I make a local reference to function

.....

function update(dt)
..
    if ctt then
      ctt()
      ctt=nil
    end
..
end

0

フォンノイマンアーキテクチャのあなたのプログラムの命令やデータが格納されている場合、コンピュータ、メモリがあります。コードを1回だけ実行する場合は、メソッドの完了後にメソッド内のコードでNOPでメソッドを上書きできます。この方法では、メソッドが再度実行される場合、何も起こりません。


6
これにより、プログラムメモリの書き込み保護やROMからの実行を行ういくつかのアーキテクチャが破壊されます。また、インストールされているものに応じて、ランダムなウイルス対策警告をオフにすることもあります。
パトリックヒューズ

0

pythonで、プロジェクトの他の人に気を引こうとしている場合:

code = compile('main code'+'del code', '<string>', 'exec')
exec code

このスクリプトはコードをデモンストレーションします:

from time import time as t

code = compile('del code', '<string>', 'exec')

print 'see code object:'
print code

while True:
    try:
        user = compile(raw_input('play with it (variable name is code) (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

exec code

print 'no more code object:'
try:
    print code
except BaseException as e:
    print e

while True:
    try:
        user = compile(raw_input('try to play with it again (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

0

もう1つの貢献は、もう少し一般的/再利用可能、少し読みやすく、保守可能ですが、他のソリューションよりも効率が低い可能性があります。

一度実行したロジックをクラスOnceにカプセル化しましょう:

class Once
{
    private Action body;
    public bool HasBeenCalled { get; private set; }
    public Once(Action body)
    {
        this.body = body;
    }
    public void Call()
    {
        if (!HasBeenCalled)
        {
            body();
        }
        HasBeenCalled = true;
    }
    public void Reset() { HasBeenCalled = false; }
}

提供されている例のように使用すると、次のようになります。

Once clickTheThingOnce = new Once(ClickTheThing);

void Update() {
    if(mousebuttonpressed) {
        clickTheThingOnce.Call(); // ClickTheThing is only called once
                                  // or until .Reset() is called
    }
}

0

静的変数の使用を検討できます。

void doOnce()
{
   static bool executed = false;

   if(executed == false)
   {
      ...Your code here
      executed = true;
   }
}

これはClickTheThing()関数自体で行うことも、別の関数を作成ClickTheThing()してif本体で呼び出すこともできます。

他のソリューションで提案されているフラグを使用するという考えに似ていますが、フラグは他のコードから保護されており、関数に対してローカルであるため他の場所で変更することはできません。


1
...ただし、これにより、「オブジェクトインスタンスごと」にコードを1回実行することができなくなります。(それがユーザーが必要とするものである場合..;))
Vaillancourt

0

Xbox Controller Inputでこのジレンマに実際に遭遇しました。まったく同じではありませんが、かなり似ています。この例のコードは、ニーズに合わせて変更できます。

編集:あなたの状況はこれを使用します->

https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse

そして、あなたは生の入力クラスを作成する方法を学ぶことができます->

https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input

しかし..今、非常に素晴らしいアルゴリズムに...本当にそうではありませんが、ちょっと..それはかなりクールです:)

*したがって...すべてのボタンの状態を保存できます。これらのボタンは、押された状態、リリースされた状態、押し下げられた状態です!!! 保留時間も確認できますが、これには単一のifステートメントが必要であり、任意の数のボタンを確認できますが、この情報についてはいくつかのルールがあります。

明らかに、何かが押されたり、離されたりしたかどうかを確認したい場合は、「If(This){}」を実行しますが、これは、プレス状態を取得して次のフレームをオフにする方法を示しています。 ismousepressed」は、次にチェックするときに実際にfalseになります。

完全なコードはこちら:https : //github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp

使い方..

したがって、ボタンが押されているかどうかを表すときに受け取る値はわかりませんが、基本的にXInputを読み込むと0から65535の16ビット値が得られ、これには「Pressed」の15ビットの状態があります。

問題は、これをチェックするたびでした。情報の現在の状態を単純に教えてくれます。現在の状態を、Pressed、Release、およびHold Valueに変換する方法が必要でした。

だから私がやったことは次のとおりです。

まず、「CURRENT」変数を作成します。このデータを確認するたびに、「CURRENT」を「PREVIOUS」変数に設定し、次に示すように「Current」に新しいデータを保存します->

uint64_t LAST = CURRENT;
CURRENT = gamepad.wButtons;

この情報があれば、ここがわくわくするところです!!

これで、ボタンが押し下げられているかどうかがわかります!

BUTTONS_HOLD = LAST & CURRENT;

これは基本的に、2つの値を比較し、両方に表示されるボタンの押下は1のままで、その他はすべて0に設定されます。

すなわち(1 | 2 | 4)&(2 | 4 | 8)は(2 | 4)を生成します。

これで、どのボタンが "HELD"ダウンされたかがわかりました。残りを取得できます。

押されるのは簡単です。「現在」の状態を保持し、押されたボタンを削除します。

BUTTONS_PRESSED = CURRENT ^ BUTTONS_HOLD;

リリースは同じですが、代わりに最後の状態と比較します。

BUTTONS_RELEASED = LAST ^ BUTTONS_HOLD;

押された状況を見てください。たとえば、現在2つありました| 4 | 8を押した。2 | 開催場所4。保留ビットを削除すると、8だけが残ります。これは、このサイクルで新しく押されたビットです。

同じことがリリース済みにも適用できます。このシナリオでは、「LAST」は1に設定されました。2 | 4.したがって、2を削除すると| 4ビット。最後のフレーム以降、ボタン1がリリースされました。

上記のシナリオは、おそらくビット比較のために提供できる最も理想的な状況であり、ifステートメントやforループを使用せずに3レベルのデータを提供します。

保持データを文書化することもしたかったので、私の状況は完全ではありませんが...基本的に、確認したい保持ビットを設定します。

したがって、Press / Release / Holdデータを設定するたびに、ホールドデータが現在のホールドビットチェックに等しいかどうかをチェックします。リセットされない場合は、現在の時刻にリセットされます。私の場合、フレームインデックスに設定しているので、フレームがいくつ押されているかがわかります。

このアプローチの欠点は、個別のホールドタイムを取得できないことですが、一度に複数のビットをチェックできます。つまり、ホールドビットを1に設定すると| 16は、1または16のいずれかが保持されていない場合、失敗します。そのため、カチカチ音をたて続けるには、これらすべてのボタンを押し続ける必要があります。

次に、コードを見ると、すてきな関数呼び出しがすべて表示されます。

したがって、この例では、ボタンの押下が発生したかどうかをチェックするだけで、ボタンの押下はこのアルゴリズムで1回しか実行できません。押すための次のチェックでは、もう一度押す前にリリースする必要があると、それ以上押すことができないため、存在しません。


他の回答で述べられているように、基本的にブールの代わりにビットフィールドを使用しますか?
ベイランクール

うん、かなり笑..
ジェレミー・トリフィロ

以下のmsdn構造体では、「usButtonFlags」にはすべてのボタン状態の単一の値が含まれますが、彼の要件に使用できます。docs.microsoft.com/en-us/windows/desktop/api/winuser/...
ジェレミーTrifilo

ただし、コントローラーボタンに関するすべてのことを言う必要があるのか​​どうかはわかりません。答えの中心的な内容は、気を散らす多くのテキストの下に隠されています。
ベイランクール

ええ、私はちょうど関連する個人的なシナリオとそれを達成するためにしたことを与えていました。確かに後でクリーンアップしますが、キーボードとマウスの入力を追加して、それに向けたアプローチを行うこともできます。
ジェレミートリフィロ

-3

愚かな安価な方法ですが、forループを使用できます

for (int i = 0; i < 1; i++)
{
    //code here
}

ループ内のコードは、ループの実行時に常に実行されます。コードの1回の実行を妨げるものは何もありません。ループまたはループなしでもまったく同じ結果が得られます。
ベイランクール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.