インターバルを含むキー入力のリピート処理の最適化

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

インターバルを含むキー入力のリピート処理の最適化

#1

投稿記事 by KRNKRS » 11ヶ月前

私は現在DXライブラリを使用してキー入力(Xbox360を用いるのでXInputを使用して)処理を実装しようとしています。
そこで、以下の様な処理を書いてみました。

コード: 全て選択

void InputManager::Update()
{
    GetJoypadXInputState(DX_INPUT_PAD1, &m_xinput);

    bool push = false;
    for (int i = 0; i < BUTTON_NUM; i++)
    {
        if (IsKeyDown(i))
        {
            m_buttonPushTime++;
            push = true;
            break;
        }
    }
    if (!push)
    {
        m_buttonPushTime = 0;
    }
}

//ボタンが押されているかを取得
bool InputManager::IsKeyDown(int key) const
{
    return m_xinput.Buttons[key] == BUTTON_DOWN;
}

//指定時間以上ボタンが押されているかをinterval間隔で取得
bool InputManager::IsKeyRepeatPush(int key, int time, int interval) const
{
    return IsKeyDown(key) &&
           m_buttonPushTime >= time &&
           m_buttonPushTime % interval == 0;
}
Is関数、Getter関数にはconstをつけなければならず、故にカウンタを関数内で加算することができない為、Update関数内で加算しています。
しかし、これだと毎フレームfor文で全ボタンを確認してまわらなければならず、凄く冗長で無駄な記述になっている感じがします。

これ以上に簡略化できるようなプログラムの記述方などはありますでしょうか。
よろしくお願いいたします。

かずま

Re: インターバルを含むキー入力のリピート処理の最適化

#2

投稿記事 by かずま » 11ヶ月前

KRNKRS さんが書きました:これ以上に簡略化できるようなプログラムの記述方などはありますでしょうか。
これでどうでしょうか?

コード: 全て選択

void InputManager::Update()
{
    GetJoypadXInputState(DX_INPUT_PAD1, &m_xinput);
    long long *p = (long long *) m_xinput.Buttons;
    if (p[0] || p[1])
        m_buttonPushTime++;
    else
        m_buttonPushTime = 0;
}

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#3

投稿記事 by KRNKRS » 11ヶ月前

正常に動作しました! ありがとうございます!

しかし、なぜこれが正常に動作するのかちょっと理解が追い付いていないので、可能であれば処理の説明を頂ければと思います。
自分なりに順序だてて考えてみたところ、

コード: 全て選択

//内容が長いのでcode囲いで
0.「m_xinput.Buttons」はunsigned char型配列。そしてこれは0,1を返す。
  つまり、中にはビットフィールドのような「0110010101001101」のように、計16個の0と1の文字羅列が並んでいるものと推測する。
  これは各ボタンが入力されているかどうかの情報である(ON:1, OFF:0)。

1.「m_xinput.Buttons」には16個の要素がある、つまり16bitであるとみなし、整数にした場合表せる範囲は0~65535であると考えられる(Wikiより)。

2.ならint型でもよいのでは? と思ったらところがどっこいintじゃ正常に値が出ない。なぜか?

3.「m_xinput.Buttons」の配列は確かに16個だが、型が「unsigned char」が肝なのでは。
  そもそも配列の中身は文字列であって二値ではない。

4.unsigned charを整数にしたときの範囲は0~255(Wikiより)なので、とりあえず頭空っぽにして「255^16」なのではと考えてみる

5.255^16 = 3.1962658e+38...????

6.そもそも「001101101...」の文字を整数にしたときに0と1判断して計算するわけじゃなくないか?(3に戻る)
と、ここまで来てこんがらがってしまい結局わからずじまいでした。
どこまでが合っていて、どこからが間違っているのでしょうか。
出来れば含めてご教授をお願いいたします。

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#4

投稿記事 by KRNKRS » 11ヶ月前

unsigned char配列[16] -> 16byteを、
long long *p -> 8byteにしている?

となると、
「0000000011111111」の文字群があったとして
「11111111」にしている?

とすると削った意味とは...

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#5

投稿記事 by KRNKRS » 11ヶ月前

チガウ...16byteであって16bitじゃない...
結局どういう事なんだ...

アバター
みけCAT
記事: 6003
登録日時: 7年前
住所: 千葉県
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#6

投稿記事 by みけCAT » 11ヶ月前

KRNKRS さんが書きました:0.「m_xinput.Buttons」はunsigned char型配列。そしてこれは0,1を返す。
  つまり、中にはビットフィールドのような「0110010101001101」のように、計16個の0と1の文字羅列が並んでいるものと推測する。
  これは各ボタンが入力されているかどうかの情報である(ON:1, OFF:0)。
「0と1の文字」というのが微妙な感じがしますね。
「0,1を返す」とのことなので、おそらく文字コードの'0'と'1'ではなく数値の0と1が入るのでしょう。
というより、GetJoypadXInputState関数のリファレンスに「0:押されていない 1:押されている」と書いてありますね。
KRNKRS さんが書きました:1.「m_xinput.Buttons」には16個の要素がある、つまり16bitであるとみなし、整数にした場合表せる範囲は0~65535であると考えられる(Wikiより)。
16bitではなく、unsigned char型が8ビットの環境では8 * 16 = 128bitです。
KRNKRS さんが書きました:2.ならint型でもよいのでは? と思ったらところがどっこいintじゃ正常に値が出ない。なぜか?
int型のサイズが8バイトでない環境では、参照しない場所があったり配列の範囲外にアクセスしたりして失敗するでしょう。
KRNKRS さんが書きました:3.「m_xinput.Buttons」の配列は確かに16個だが、型が「unsigned char」が肝なのでは。
  そもそも配列の中身は文字列であって二値ではない。
全部のボタンが押されている場合、NUL文字で終端された「文字列」になりません。
また、前述の通りリファレンスに「0:押されていない 1:押されている」と書いてあるので、二値であると思えます。
KRNKRS さんが書きました:4.unsigned charを整数にしたときの範囲は0~255(Wikiより)なので、とりあえず頭空っぽにして「255^16」なのではと考えてみる
255^16ではなく、0も含めた256通りなので、256^16です。
KRNKRS さんが書きました:5.255^16 = 3.1962658e+38...????
Windows 7の電卓で計算した結果、^が累乗を表す演算子だとすれば正しいでしょう。
ちなみに、^がXORを表す場合は155^16 = 239です。
KRNKRS さんが書きました:6.そもそも「001101101...」の文字を整数にしたときに0と1判断して計算するわけじゃなくないか?(3に戻る)
よくわかりません。
KRNKRS さんが書きました:unsigned char配列[16] -> 16byteを、
long long *p -> 8byteにしている?
p[0]とp[1]を参照しているので、8byte * 2 = 16byteのままです。
KRNKRS さんが書きました:となると、
「0000000011111111」の文字群があったとして
「11111111」にしている?
「00000000」と「11111111」に分けて処理をしているようです。
KRNKRS さんが書きました:とすると削った意味とは...
128bitの整数型が無い環境に対応するためでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#7

投稿記事 by KRNKRS » 11ヶ月前

あっ!!
分かった気がします!

つまり、

1.unsigned char button[16] の中身が {'0', '1', '1', '0', ....} と16個の文字が連なっているのを、
2.long long p* にキャストすることで { {'0', '1', '1', '0', ....(8個)}, {'1', '0', '1', '1', ....(8個)} }に分割し、
3.「||」で二つをOR演算することで、「1」を取り出している。

というわけでしょうか!?

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#8

投稿記事 by KRNKRS » 11ヶ月前

よく考えたらlong long型なので違いました。
「いづれかの箇所に1が入っている」 = 「0以外の値となる」 = 「trueとみなす」
となると思われるので、それを二つ行っているわけですね!

で、16bitのデータ型は無いのでlong longポインタで賄っているわけですね!
そういう手法があったとは初耳でした...

ありがとうございます!!
最後に編集したユーザー KRNKRS on 2017年5月14日(日) 20:43 [ 編集 1 回目 ]

かずま

Re: インターバルを含むキー入力のリピート処理の最適化

#9

投稿記事 by かずま » 11ヶ月前

unsigned char Button[16] = { 0,1,1,0,0,0,0,0,1,0,1,1,0,0,0,0 };
と 16個の数値が連なっているのを
long long a[2] = { 0x0001010000000000, 0x0100010100000000 };
だと解釈するために、long long *p = a; を実行し
p[0] で、まず a[0] がゼロであるかどうかを見て、
それが、ゼロでなかったら、すぐに m_buttonPushTime++; を実行する。
ゼロだったら、p[1] で、a[1] がゼロであるかどうかを見て、
それが、ゼロでなかったら、すぐに m_buttonPushTime++; を実行する。
p[0] も p[1] もゼロだったら、m_buttonPushTime = 0; を実行する。

KRNKRS
記事: 40
登録日時: 4年前
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#10

投稿記事 by KRNKRS » 11ヶ月前

> かずま
まさかchar文字群がキャストされると0x~~と解釈されるとは思いもよりませんでした...

ありがとうございます!

かずま

Re: インターバルを含むキー入力のリピート処理の最適化

#11

投稿記事 by かずま » 11ヶ月前

かずま さんが書きました:unsigned char Button[16] = { 0,1,1,0,0,0,0,0,1,0,1,1,0,0,0,0 };
と 16個の数値が連なっているのを
long long a[2] = { 0x0001010000000000, 0x0100010100000000 };
だと解釈するために、long long *p = a; を実行し
最近の CPU はリトルエンディアンが多いので、
long long a[2] = { 0x0000000000010100, 0x0000000001010001 };

アバター
みけCAT
記事: 6003
登録日時: 7年前
住所: 千葉県
連絡を取る:

Re: インターバルを含むキー入力のリピート処理の最適化

#12

投稿記事 by みけCAT » 11ヶ月前

KRNKRS さんが書きました:1.unsigned char button[16] の中身が {'0', '1', '1', '0', ....} と16個の文字が連なっているのを、
'0', '1', ... という文字 (ASCIIの場合48, 49, ... という数値) ではなく、0, 1, ...という数値でしょう。
KRNKRS さんが書きました:2.long long p* にキャストすることで { {'0', '1', '1', '0', ....(8個)}, {'1', '0', '1', '1', ....(8個)} }に分割し、
1.と同様にデータが違います。
KRNKRS さんが書きました:3.「||」で二つをOR演算することで、「1」を取り出している。
「『1』を取り出している」の意味がよくわからないですが、||演算子の結果を整数にしたものは両辺がともにfalseなるならfalse、そうでなければtrueになります。
整数は0はfalse、0以外はtrueになります。

N3337 4.12 Boolean conversionsより引用
1
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a
prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false;
any other value is converted to true.
N3337 5.15 Logical OR operatorより引用
1
The || operator groups left-to-right. The operands are both contextually converted to bool (Clause 4). It
returns true if either of its operands is true, and false otherwise. Unlike |, || guarantees left-to-right
evaluation; moreover, the second operand is not evaluated if the first operand evaluates to true.
KRNKRS さんが書きました:よく考えたらlong long型なので違いました。
long long型は関係なく違います。
KRNKRS さんが書きました:いづれかの箇所に1が入っている」 = 「0以外の値となる」 = 「trueとみなす」
となると思われるので、それを二つ行っているわけですね!
そのようですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

返信

“C言語何でも質問掲示板” へ戻る