Skip to content

Commit 8367870

Browse files
committed
perf: refactor oscilloscope loop to prevent double-scheduling and sync with global idle state
1 parent f29c00f commit 8367870

File tree

5 files changed

+180
-114
lines changed

5 files changed

+180
-114
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Auto-convert PO to JSON and commit
2+
3+
on:
4+
push:
5+
paths:
6+
- 'po/**/*.po'
7+
8+
jobs:
9+
convert-and-commit:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
with:
16+
persist-credentials: true
17+
fetch-depth: 0
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.x'
23+
24+
- name: Find changed .po files
25+
id: find_po
26+
run: |
27+
git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '^po/.*\.po$' > changed_po_files.txt || true
28+
cat changed_po_files.txt
29+
echo "po_files<<EOF" >> $GITHUB_OUTPUT
30+
echo "$(cat changed_po_files.txt)" >> $GITHUB_OUTPUT
31+
echo "EOF" >> $GITHUB_OUTPUT
32+
33+
- name: Run conversion script
34+
if: steps.find_po.outputs.po_files != ''
35+
run: |
36+
mkdir -p locales
37+
while IFS= read -r po_file; do
38+
echo "▶ Converting $po_file"
39+
python3 convert_po_to_json.py "$po_file" "locales"
40+
done < changed_po_files.txt
41+
42+
- name: Commit and push updated JSON
43+
if: steps.find_po.outputs.po_files != ''
44+
run: |
45+
git config --global user.name "github-actions[bot]"
46+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
47+
48+
git add locales/*.json
49+
50+
if git diff --cached --quiet; then
51+
echo "✅ No JSON changes to commit."
52+
else
53+
git commit -m "chore(i18n): auto-update JSON files from updated PO files"
54+
git push origin ${{ github.ref }}
55+
echo "🚀 Pushed updated JSON files."
56+
fi

js/activity.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,13 +2756,13 @@ class Activity {
27562756
const IDLE_FPS = 1;
27572757

27582758
let lastActivity = Date.now();
2759-
let isIdle = false;
2759+
this.isAppIdle = false;
27602760

27612761
// Wake up function - restores full framerate
27622762
const resetIdleTimer = () => {
27632763
lastActivity = Date.now();
2764-
if (isIdle) {
2765-
isIdle = false;
2764+
if (this.isAppIdle) {
2765+
this.isAppIdle = false;
27662766
createjs.Ticker.framerate = ACTIVE_FPS;
27672767
// Force immediate redraw for responsiveness
27682768
if (this.stage) this.stage.update();
@@ -2782,12 +2782,12 @@ class Activity {
27822782
const isMusicPlaying = this.logo?._alreadyRunning || false;
27832783

27842784
if (!isMusicPlaying && Date.now() - lastActivity > IDLE_THRESHOLD) {
2785-
if (!isIdle) {
2786-
isIdle = true;
2785+
if (!this.isAppIdle) {
2786+
this.isAppIdle = true;
27872787
createjs.Ticker.framerate = IDLE_FPS;
27882788
console.log("⚡ Idle mode: Throttling to 1 FPS to save battery");
27892789
}
2790-
} else if (isIdle && isMusicPlaying) {
2790+
} else if (this.isAppIdle && isMusicPlaying) {
27912791
// Music started playing - wake up immediately
27922792
resetIdleTimer();
27932793
}

js/widgets/oscilloscope.js

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class Oscilloscope {
4747
// RAF lifecycle control
4848
this._running = false;
4949
this._rafId = null;
50+
this._timeoutId = null;
51+
this._isIdle = false;
5052
this.draw = this.draw.bind(this);
5153

5254
this.pitchAnalysers = {};
@@ -111,10 +113,14 @@ class Oscilloscope {
111113

112114
if (this._rafId !== null) {
113115
cancelAnimationFrame(this._rafId);
114-
clearTimeout(this._rafId);
115116
this._rafId = null;
116117
}
117118

119+
if (this._timeoutId !== null) {
120+
clearTimeout(this._timeoutId);
121+
this._timeoutId = null;
122+
}
123+
118124
// Backward compatibility: if any per-turtle RAF ids were stored, cancel them too.
119125
for (const id of Object.values(this.drawVisualIDs || {})) {
120126
if (id !== null && id !== undefined) {
@@ -130,18 +136,39 @@ class Oscilloscope {
130136
}
131137

132138
_throttle() {
133-
if (!this._running || this._rafId === null) return;
134-
cancelAnimationFrame(this._rafId);
135-
clearTimeout(this._rafId);
136-
this._rafId = setTimeout(this.draw, 1000);
139+
if (!this._running) return;
140+
141+
// Cancel any active RAF
142+
if (this._rafId !== null) {
143+
cancelAnimationFrame(this._rafId);
144+
this._rafId = null;
145+
}
146+
147+
// Enter idle mode
148+
this._isIdle = true;
149+
150+
// Start timeout scheduler if not already running
151+
if (this._timeoutId === null) {
152+
this._timeoutId = setTimeout(this.draw, 1000);
153+
}
137154
}
138155

139156
_wakeUp() {
140-
if (!this._running || this._rafId === null) return;
141-
cancelAnimationFrame(this._rafId);
142-
clearTimeout(this._rafId);
143-
this._rafId = null;
144-
this.draw();
157+
if (!this._running) return;
158+
159+
// Cancel any active timeout
160+
if (this._timeoutId !== null) {
161+
clearTimeout(this._timeoutId);
162+
this._timeoutId = null;
163+
}
164+
165+
// Exit idle mode
166+
this._isIdle = false;
167+
168+
// Restart RAF scheduler if not already running
169+
if (this._rafId === null) {
170+
this.draw();
171+
}
145172
}
146173

147174
_handleVisibilityChange() {
@@ -245,14 +272,22 @@ class Oscilloscope {
245272
draw() {
246273
if (!this._running) return;
247274

248-
if (document.visibilityState === "hidden" || this.widgetWindow._rolled) {
249-
this._rafId = setTimeout(this.draw, 1000);
250-
return;
251-
}
252-
275+
// Render the current frame
253276
this._renderFrame();
254277

255-
this._rafId = requestAnimationFrame(this.draw);
278+
// Schedule next frame based on idle state
279+
if (
280+
this._isIdle ||
281+
(this.activity && this.activity.isAppIdle) ||
282+
document.visibilityState === "hidden" ||
283+
this.widgetWindow._rolled
284+
) {
285+
// Use setTimeout for idle mode (1 FPS)
286+
this._timeoutId = setTimeout(this.draw, 1000);
287+
} else {
288+
// Use RAF for active mode (~60 FPS)
289+
this._rafId = requestAnimationFrame(this.draw);
290+
}
256291
}
257292

258293
/* ---------------- Resize ---------------- */

js/widgets/pitchdrummatrix.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -160,28 +160,22 @@ class PitchDrumMatrix {
160160
* @private
161161
*/
162162
this._save_lock = false;
163-
widgetWindow.addButton(
164-
"export-chunk.svg",
165-
PitchDrumMatrix.ICONSIZE,
166-
_("Save")
167-
).onclick = () => {
168-
// Debounce button
169-
if (!this._get_save_lock()) {
170-
this._save_lock = true;
171-
this._save();
172-
setTimeout(() => {
173-
this._save_lock = false;
174-
}, 1000);
175-
}
176-
};
163+
widgetWindow.addButton("export-chunk.svg", PitchDrumMatrix.ICONSIZE, _("Save")).onclick =
164+
() => {
165+
// Debounce button
166+
if (!this._get_save_lock()) {
167+
this._save_lock = true;
168+
this._save();
169+
setTimeout(() => {
170+
this._save_lock = false;
171+
}, 1000);
172+
}
173+
};
177174

178-
widgetWindow.addButton(
179-
"erase-button.svg",
180-
PitchDrumMatrix.ICONSIZE,
181-
_("Clear")
182-
).onclick = () => {
183-
this._clear();
184-
};
175+
widgetWindow.addButton("erase-button.svg", PitchDrumMatrix.ICONSIZE, _("Clear")).onclick =
176+
() => {
177+
this._clear();
178+
};
185179

186180
/**
187181
* The container for the pitch/drum matrix.

0 commit comments

Comments
 (0)