Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

キーボードのリピート速度設定を取得後にミリ秒に変換せずにタイマーの実行間隔に設定している #506

Closed
beru opened this issue Sep 30, 2018 · 11 comments
Labels
🐛bug🦋 ■バグ修正(Something isn't working)
Milestone

Comments

@beru
Copy link
Contributor

beru commented Sep 30, 2018

SystemParametersInfo( SPI_GETKEYBOARDSPEED, 0, &nKeyBoardSpeed, 0 );

ここで、SystemParametersInfo 関数に SPI_GETKEYBOARDSPEED を指定してキーボードのリピート速度設定を取得後に、取得した値をそのまま SetTimer 関数のタイムアウト値に渡しています。

if( 0 == ::SetTimer( GetHwnd(), IDT_ROLLMOUSE, nKeyBoardSpeed, EditViewTimerProc ) ){

SetTimer 関数の第3引数の uElapse の単位はミリ秒ですが、SystemParametersInfo 関数に SPI_GETKEYBOARDSPEED を指定して取得する値の単位はミリ秒ではありません。

https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-systemparametersinfoa
の記載を引用します。

Retrieves the keyboard repeat-speed setting, which is a value in the range from 0 (approximately 2.5 repetitions per second) through 31 (approximately 30 repetitions per second). The actual repeat rates are hardware-dependent and may vary from a linear scale by as much as 20%. The pvParam parameter must point to a DWORD variable that receives the setting.

そして、こちらは SetTimer 関数のドキュメントのリンクです。
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-settimer

つまるところ、不必要に高い頻度でタイマーのコールバック関数 EditViewTimerProc が呼び出される事となっています。

@ds14050
Copy link
Contributor

ds14050 commented Sep 30, 2018

ハードウェア依存だけども概ねリピート間隔が 400ms から 33ms の間で変化するみたいですね。

may vary from a linear scale by as much as 20%.

よくわかりません。都合良く単語だけをピックアップすると、0 から 31 の設定値は等間隔に分布していて(=リピート間隔が線形に変化する)、実際のリピート間隔のずれは 20% 以内に収まる、みたいな。

key repeat interval (msec) = 400 - x*(400-33)/31

上限が本当に 31 か、思わず不安になる式です。

@beru
Copy link
Contributor Author

beru commented Sep 30, 2018

お、その式で確かにミリ秒に変換出来そうですね。整数演算で済ませてて良いですね。
値の範囲が 0 ~ 31 とドキュメントに書かれているので念の為にクリッピングしてからその式にかければ安心だと思います。

Excel で確認してみたらきちんと推移していたのでその式で問題無いと思います。

image

@ds14050
Copy link
Contributor

ds14050 commented Oct 1, 2018

Excel で確認してみたら

しゅごい(感嘆)。

@beru
Copy link
Contributor Author

beru commented Oct 3, 2018

Excel は有料なので R とかのが良かったですね。。

@KENCHjp
Copy link
Member

KENCHjp commented Oct 3, 2018

Google docとかでも開けるから大丈夫かと!

@ds14050
Copy link
Contributor

ds14050 commented Oct 3, 2018

割る31が惜しい気がしました。

key repeat interval (msec) = 400 - x*379/32

Excel でも R でもなんでもいいんですが、そういう可視化ツールを自分の道具箱に持っておいて、さっと出して使えるところがすごいんです。自分は端点が 33 と 400 になることだけを確認して、式が1次関数になっているかの確認すら怠りました。

@beru
Copy link
Contributor Author

beru commented Oct 3, 2018

コンパイラが自動的に最適化してくれると思いますが、31 で割るのが惜しい場合は

key repeat interval (msec) = 400 - ((x * 24827917) >> 21)

ただコメントが無いと可読性が…

@beru
Copy link
Contributor Author

beru commented Oct 3, 2018

Excel でも R でもなんでもいいんですが、そういう可視化ツールを自分の道具箱に持っておいて、さっと出して使えるところがすごいんです。自分は端点が 33 と 400 になることだけを確認して、式が1次関数になっているかの確認すら怠りました。

自分は浮動小数点演算を行う lerp 関数を用意して…とか考えていたので整数演算で行うという発想を怠ってしまってました。

Win + r キーで excel の起動はすぐ出来るのでさっと出すのは簡単だと思います。ただ何でもExcelに頼ろうとしてしまうのが我ながら社畜っぽい振る舞いですね。。

@ds14050
Copy link
Contributor

ds14050 commented Oct 4, 2018

key repeat interval (msec) = 400 - ((x * 24827917) >> 21)

理解できませんがクリッピングも込みですか? と思ったが違いました。区間を大きくとった方が小数点以下の切り捨て誤差が小さいと? 20%のぶれがあるのだから計算しやすい式を選ぶ自由がありますね。しかし 379 を (512-128) で近似して何かできないかとかつらつらお風呂で考えていて気がつきましたが、HTML DOM の setTimeout とは違って、むしろ setInverval に似て、初期化時に1回呼ばれるだけの処理でした……。

@beru
Copy link
Contributor Author

beru commented Oct 4, 2018

理解できませんがクリッピングも込みですか? と思ったが違いました。区間を大きくとった方が小数点以下の切り捨て誤差が小さいと? 20%のぶれがあるのだから計算しやすい式を選ぶ自由がありますね。しかし 379 を (512-128) で近似して何かできないかとかつらつらお風呂で考えていて気がつきましたが、HTML DOM の setTimeout とは違って、むしろ setInverval に似て、初期化時に1回呼ばれるだけの処理でした……。

クリッピングに関してはドキュメントの方で 0~31 の範囲で返しますよ、と謳っているので不要だとは思います。念のためにやっておいても良いのかもしれませんが…。

自分が書いた変な定数と掛け算してから右シフトしているのは、割り算を乗算と右シフトに置き換える場合に計算結果の誤差が出ないようにトライ&エラーで調整してみました。なお、分母が定数の整数除算を乗算と右シフトに置き換えするのは確立された方法なので大体のコンパイラがやってくれると思います。

こちらのコメント #506 (comment) の方法だと 25 の場合にミリ秒の値が 104 となっていて1低い値が出ています。

#506 (comment) のコメントの式であれば値はずれていないようです。

テストに使ったコード

#include <stdio.h>

static inline unsigned int div31(unsigned short numerator)
{
	assert(numerator < 63489);
	return (numerator * 67651) >> 21;
}

int main(int argc, char* argv[])
{
	for (unsigned int i=0; i<32; ++i) {
		unsigned int quotient1 = 400 - div31(i * (400-33));
		unsigned int quotient2 = 400 - i * (400 - 33) / 31;
		unsigned int quotient3 = 400 - i * 379 / 32;
		printf("%d %d %d %d\n", i, quotient1, quotient2, quotient3);
	}
}

出力結果

0 400 400 400
1 389 389 389
2 377 377 377
3 365 365 365
4 353 353 353
5 341 341 341
6 329 329 329
7 318 318 318
8 306 306 306
9 294 294 294
10 282 282 282
11 270 270 270
12 258 258 258
13 247 247 247
14 235 235 235
15 223 223 223
16 211 211 211
17 199 199 199
18 187 187 187
19 176 176 175
20 164 164 164
21 152 152 152
22 140 140 140
23 128 128 128
24 116 116 116
25 105 105 104
26 93 93 93
27 81 81 81
28 69 69 69
29 57 57 57
30 45 45 45
31 33 33 33

ドキュメントに書かれている通り環境によってぶれがあるようですし、呼び出し回数も少ないので全然こだわるところでないのはおっしゃる通りです。それに小数点以下の四捨五入とかもやっていないですね。まぁ大体で問題無いですね。

@beru
Copy link
Contributor Author

beru commented Oct 7, 2018

#523 を Merge したので Close します。
計算式を教えてくれた @ds14050 さんに感謝します。

@beru beru closed this as completed Oct 7, 2018
@m-tmatma m-tmatma added this to the next release milestone Oct 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛bug🦋 ■バグ修正(Something isn't working)
Projects
None yet
Development

No branches or pull requests

4 participants