firefox (+拡張機能 ublock origin)
google chrome
なんにも見えん
同条件で比較してないからずるい?いや、ずるくない。そもそもandroidのchromeにublockを入れられないのが悪い。
firefoxにも色々問題はあるが、広告で何も見えないことに比べたら全て無視できる。
なんにも見えん
同条件で比較してないからずるい?いや、ずるくない。そもそもandroidのchromeにublockを入れられないのが悪い。
firefoxにも色々問題はあるが、広告で何も見えないことに比べたら全て無視できる。
以下のあたりの続き
https://tthhfugapiyo.blogspot.com/2025/04/5linuxanbernic-353ps.html
https://tthhfugapiyo.blogspot.com/2025/04/cfor.html
sega saturnのエミュレータでGRANDIAのムービーが遅い原因がSCUのエミュレーションにあるということまで突き止めたが、どうにもならんという結論になっていた。 30FPSくらいまでしか出ず、スロー再生になる。
今ならAIがめんどくさいこともやってくれるんじゃないかという気がして再チャレンジ。
とりあえず無料枠内でclaudeやらgithub copilotやらに指示を出しまくって改善をやらせた。「mednafenみたいに関数ポインタで分岐なくしてみて」とかやらせてみようとしたが、改造が大規模すぎるということで拒否される。多分無料枠内だと無理。(これをやらせたかったんだが。)
GRANDIA特化でSCUコードを埋め込ませたりして無理やり時間を縮めてみたが、45FPSくらいが限界そう。というかよく計算したらSCUの処理時間が0になってようやく60FPSが達成できるので、そもそも無理があった。
SCUとCPU(SH2)は結構密接に絡んでいるらしく、それぞれ数サイクルずつ順番に処理していく動作となっている。これをSCUが遅いからと言ってそのままSCUを別スレッドにしてCPUと同期を取りながら進めると同期のコストで逆に激しく遅くなる。
もうシミュレーション精度はある程度切り捨て、完全に元コードの通り同期させるのは諦めて、ある程度のサイクル数まとめてスレッドで動かすようにした。(グランディアはそれである程度動くっぽい。)
1 フレーム分をまとめてしまうと、CPUがなにかSCUを待っているらしく、かなり遅くなってしまったので、適当に40000サイクルずつくらいSCUを動かすという感じ。もうめんどくさくなって動けばいいやという動きになっている。
スレッド間の排他とか何もやってないが、結構動いている。 もともとスレッド前提で書かれていたのか、たまたま奇跡的に動いているのか。
AIをしばき回してチリツモで稼いだ時間と、スレッド化によってムービーが60FPSくらいで動くようになった。(https://github.com/tthogehoge/yabause/releases/tag/grandia)
苦節1年、なんとか遊べるようになった気がする。ただ、オープニングムービーとちょっとした戦闘くらいしかテストしてない。色々変えたので動かない箇所が出てくるかもしれない。コードの整理もできてないが、もはや疲れたのでおしまい。 めでたしめでたし。
誰も住んでない実家の草取りが面倒くさい。草が生え始めたくらいのときに除草剤を撒けば、ある程度マシになるかもしれない。が、誰も住んでないのでタイミングがつかめない。
一日一回でいいのでカメラで確認できるといいが、そのためだけにwifiやら電気を通すのは勿体ない。スタンドアロンでなんとかならないか。
とりあえずESP32マイコンでsimが使えそうなLILYGO T-SIM7080G-S3というのを見つけたのでやってみようとした。
用途にちょうど良さそうってことでT-SIM7080G-S3を勢いで買ってしまったが、届くまでの間に検索しているとNB-IoT?とかCAT-M1?とやらで普通のSIMは使えないという話が見えてきた。
CAT-M1とかのSIMで探すと、1NCE?とかいくつか出てくるが、大半は業務用で、個人では契約できなそう。実質SORACOM一択なのではないかという気がしてくる。
7670Gとかにしておけばよかったのかも。微妙に高かったっから7080Gにしてしまった。致命的なミス。
まあ買ってしまったものは仕方ないので、試しに別件で買ったプリペイドSIMを入れてみたらなぜか動いた。普通にスマホで使っているbiglobeのSIMは動かなかった。期限切れになって新しく買ったプリペイドSIMも動いたので、IIJ-docomoのプリペイドはCAT-M1対応しているのかもしれない。金を無駄にする可能性があるので、他は試してない。
書くのが面倒だったので、claudeに投げてみた。
これだけで割とベースは作ってくれる。課金してないので自動でログを読んで修正とかまではしてくれないが、ログを渡すと無課金でもかなりの部分を直してくれる。
claudeは初めて使ってみたが、「DERPサーバー(tailscaleのサーバーらしい)のリージョンが違う?」とか「タイムスタンプがuptimeになっていてサーバーで無視されている」とか素人には分かりようもない原因をバンバン当ててくる。いや、すげーな。話題になるだけある。プログラマなんか不要になるというのも頷ける。課金したくなるのも分かる気がする。
ということでclaudeの無料枠を使い切ったらgithub-copilotを使って、という感じで作っていった。(https://github.com/tthogehoge/t-sim7080g-s3_tailscale_sample)
機能はざっくり以下の様な感じ。ほぼやりたいことはできた。
microlinkについているhttpサーバにカメラ画像を入れただけ。
何か温度も表示されている。マイコンとSIMくらいしか見てないが割と70℃くらいまでは大丈夫そう?消費電力は100mAちょいくらい、160くらいまでは行くこともある。5秒周期で2秒位100mA超えで高くて、残り低いときは30〜50mAくらい。多分eDRXとか言うやつで制御されてそう。
USBのソーラー電源を買ってきて、保冷箱とかに入れて日陰においておけば、何かいい感じになるんじゃないだろうか?なるといいが。
ちなみにT-SIM7080G-S3は充電セルを入れるソケットもあってソーラー充電可能らしいが、火事になりそうでなんか怖いので入れない予定。
あとは、半年ごとにプリペイドsimを入れ替えるのかSORACOMを契約するのかという問題が残っている。うーむ。
楽天の株主優待でeSIMがもらえるらしい。子供に持たせるスマホには良さそうだが、お下がりの古い端末ではeSIMは使えない。
eSIMをSIMに入れることができるアダプタみたいなものが売ってるようなので、使えるかどうか試した。
色々あるようだが、SIM geekとかいうやつを買ってみた。(写真は抜け殻。nanoSIMで使ったが、他のサイズでも使えそう。やってみた感じでは、テープとかで補強すれば再度組み立てることもできなくはなさそう。)
スマホに入れてこのsimの中身を書き換えるようだ。EasyEUICCとか言うアプリを推奨と書いてあったが、google playストアには無いようだった。直に入れれば良さそうだが、面倒だったので、かわりにそれを使った商品?の9eSIMとか言うやつ用のアプリがgoogle playストアにあったのでそれで入れてみた。
eSIMは使ったこと無いので勝手がわからないが、とりあえず適当に一番安そうなやつを買ってみる。syesim?とかいうところのeSIMのようだ。amazonで発送済みくらいのステータスになったら(1日半くらい待った)、 activationのページで登録する。
メールでQRコードが送られてくるので、9eSIMとかいうソフトで読んで追加する。
simフリー端末だったからか、APNは自動で設定されて通信できた。(メールに書いてあるAPNと違う設定だったんだけど、なぜか繋がったようだ)
後日封筒でも上記の説明書が送られてくるが、必要ない。
期限が切れたあと1日くらいは使えたような気がする。
本当は楽天のeSIMで試さないと意味なさそうだが、とりあえずは良しとする。
QtつかっててQThreadとタイマー使ってると以下のエラーが出ることがある。
QObject::~QObject: Timers cannot be stopped from another thread
何がなんだか分からないまま闇雲になんとかしてたけど、なんか気持ち悪いので調べ直した。素人調べなので間違っているかもしれない。
そもそもQThreadがどういうものなのか?調べた限りでは「slotで動かしたものがスレッドで動く」という仕組みのもののようだ。
class Worker : public QObject
{
/* 略 */
public slots:
void exec();
void exec2();
};
こんなのがあったとして、MainWindowから使う場合、
// worker作る
auto obj = new Worker();
// スレッドに移動
auto thr = new QThread();
obj->moveToThread(thr);
thr->start();
obj->exec(); // MainWindowのスレッドで動く
obj->exec2(); // MainWindowのスレッドで動く
// connect & emit
connect(this,&MainWindow::exec,obj,&Worker::exec);
connect(this,&MainWindow::exec2,obj,&Worker::exec2);
emit exec(); // Workerのスレッドで動く
emit exec2(); // Workerのスレッドで動く
emitでexec,exec2をslotで動かした場合にWorkerスレッドで動く 。execが終わるまではexec2は動かないようなので、同一スレッド内では通常は同時に動かないっぽい。
※ ただし、QEventloopでイベントを受け付けたりするとその限りではないようだ。QTcpSocketとかのwaitForConnect中とかでも他のスロットが動いたりした。内部でイベントループがあるのかもしれない。同時に動いて困る場合は念の為QMutexとかで排他したほうが良いだろう。
サンプルで、startedのsignalにconnectしたりしてるのをよく見る。下記の例だと単にthr->startを実行する時にexecも実行するというだけでしかない。
ちなみにobj->quit()したときにスロットを実行するsignalでfinishedというのもある。
以下だとexecは2回動く(はず)。
connect(thr, &QThread::started, obj, &Worker::exec);
connect(thr, &QThread::finished, obj, &Worker::exec);
thr->start();
thr->quit();
WorkerをQThreadの子クラスにするやり方もあるようだが、上記の使い方だとそんなに違いはない。
class Worker : public QThread
以下略
// worker作る
auto obj = new Worker();
// スレッドに移動
obj->moveToThread(obj);
obj->start();
obj->exec(); // MainWindowのスレッドで動く
// connect & emit
connect(this,&MainWindow::exec,obj,&Worker::exec);
emit exec(); // QThreadのスレッドで動く
QThreadを継承しても、moveToThreadするまではMainWindowのスレッドで動くっぽい。moveToThreadしないとQThreadの意味はない。上記はMainWindow側でmoveToThreadをやっているが、Workerのコンストラクタでやっても良さそう。
この使い方なら別でスレッドオブジェクトを作らなくていいくらいしか違いはなさそうだが、runをオーバーライドする場合は違いがある。
class Worker : public QThread
{
/* 略 */
void run() override;
};
この場合はthr->start();するだけでrunが実行され、関数終了でsignal finishedが発行される。よく見る例で便利そうに見えるが、run以外の処理を実行するにはQEventloopを使わないといけない。関数一つだけ別スレッドで動かすやり方のようだ。関数一つだけのためにクラスを作らないといけない。こういう用途だとQtConcurrent使ったほうが簡単じゃないのか。個人的にはあんまり実用的じゃない気がする。
さて、このQThread上でタイマーを使おうとしたとき、 「Timers cannot be stopped from another thread」と出てきて、なんでだよこのクソ野郎が!と思うことが稀によくある。
以下のような場合だ。QtのタイマーはQObjectに備え付けのstartTimer/killTimerとQTimerのstart/stopを使ってみている。
class Worker : public QThread
{
Q_OBJECT
QTimer mtimer;
int m_id;
void timerEvent(QTimerEvent *event) override;
public slots:
void exec();
/* 略 */
};
Worker::Worker(QObject *parent)
: QThread{parent}
{
m_id = startTimer(200);
connect(&mtimer,&QTimer::timeout,this,&Worker::exec);
mtimer.start(200);
}
Worker::~Worker()
{
mtimer.stop();
killTimer(m_id);
}
MainWindowから以下のように使う
auto obj = new Worker();
obj->moveToThread(obj);
// start thread
obj->start();
QThread::msleep(1000);
// end
qDebug() << "stop thread";
obj->quit();
obj->wait();
qDebug() << "delete worker";
delete obj;
deleteするときにmtimerは止められるが、killTimerはエラーになる。 逆にQThread上でタイマーを止めようとするとmtimerは止められず、killTimerは止められる。mtimerはWorkerのスレッドではなく、コンストラクタを動かしたMainWindowのスレッドで動いているということになる。
moveThreadでobjはQThreadに移動されたが、その中のmtimerは移動してくれなかったということだ。
MainWindowからしかstart/stopしないという使い方の場合はむしろこのままmtimerを使うというのでも良いのかもしれないが、以下のようにmtimer作成時に親を自分に設定すると、moveThreadのときに一緒に子どもであるmtimerも動いてくれる。
そうでなければコンストラクタを動かしたMainWindowが親になってしまう。
Worker::Worker(QObject *parent)
: QThread{parent},mtimer(this)
{
/* 略 */
これでスレッドの中ではstart/stopができるようになるが、やはりデストラクタでは止められない。むしろkillTimerもmtimer.stopも両方NGになる。
結局はこれらもデストラクタでストップするのではなく、スレッドの中で止めなければならないということだ。停止するslotを作成し、slotで止めてからdeleteすれば問題ない。
connect(this,&MainWindow::kikktimer,obj,&Worker::killtimer);
connect(this,&MainWindow::mtimer_stop,obj,&Worker::mtimer_stop);
// stop timer
emit mtimer_stop(); // タイマーを止めるslot
emit killtimer(); // タイマーを止めるslot
QThread::msleep(1000);
// end
qDebug() << "stop thread";
obj->quit();
obj->wait();
qDebug() << "delete worker";
delete obj;
runをオーバーライドしている場合はイベントループとか使わないと別のスロットを動かせない。何かフラグを立てて、run関数の中で止めるか、finishedシグナルでrun関数が終わったあとに何とかするしかない。
runをオーバーライドしているとか、面倒くさいからスレッドが終わったら止まれよ。という人向けにfinishedシグナルがあるんだろう。finishedでタイマーを止める。
connect(obj,&QThread::finished,obj,&Worker::killtimer);
connect(obj,&QThread::finished,obj,&Worker::mtimer_stop);
// end
qDebug() << "stop thread";
obj->quit();
obj->wait(); // この辺でタイマーが止まる
qDebug() << "delete worker";
delete obj;
終わったらdeleteもやれよという人にはdeleteLaterがあるが、ちょっと罠がある。
connect(obj,&QThread::finished,obj,&Worker::killtimer);
connect(obj,&QThread::finished,obj,&Worker::mtimer_stop);
connect(obj,&QThread::finished,obj,&Worker::deleteLater);
// end
qDebug() << "stop thread";
obj->quit();
// obj->wait(); // これやると死ぬ
obj->quit()した時点でkillTimer/mtimer_stop/deleteがWorkerスレッドのタイミングで始まる。MainWindowスレッドのobj->waitしている間にdeleteまでされるので、obj->wait実行中にobjが消えることになる。finishedでdeleteLaterを呼ぶ場合はquitしたらもうobjには触ってはいけないということだろう。
一回スタートしたらあとはsignal/slotで自動で消えてくれる。Qtをうまく活用した処理と言えるけど、個人的にはスレッドが終わるのを確認しないと気持ち悪いし、ちゃんと自分でdeleteしないと気持ち悪いんだけどな。
自宅wifiでもtailscale DNSの名前を使おうとして四苦八苦したが、中途半端にできた状態でうまく行ったとは言い難い。
DNSの名前を使うのは諦めて、local IPでアクセスすることにした。
自宅内のraspiサーバーでtailscaleを起動する。このとき192.168.11.0/24を外からアクセスできるようにアドバタイズする。
tailscale up --advertise-routes=192.168.11.0/24
tailscaleのページでedit routes settingから、アドバタイズしたsubnet routesをチェックして許可?する。
ついでにexit nodeにしても良さそうだが、しなくても良さそう?exit nodeにすれば自宅サーバ経由でインターネットアクセスができる。が、家のネットはあんまり早くないので積極的に使うほどでもない。怪しげなパブリックwifiを使うときとかに使えたら便利そうなので一応設定しておく。
tailscale up --advartise-exit-node --advertise-routes=192.168.11.0/24
exit nodeにする場合もtaiscaleのページから確定する必要がありそう。
ローカルLANにアクセスしたいだけなら、exit nodeのところからAllow LAN accessをONにすれば良さそう。
exit node経由でネットアクセスしたいなら、exit nodeで上記設定したサーバを使うよう設定する。
これだけで良かったのか。苦労したのは何だったのか。。
以前作ったモデルをモーションキャプチャーで動かしたかった。
ちょっと調べてみたところXR_Animatorとかいうソフトがあるらしい。electronで作ってあり、windowsでもlinuxでも動くようなのでやってみた。
起動して、vrmファイルをドラッグアンドドロップする。
一番左のアイコンをダブルクリックする。
できた。簡単。