refactor(labs): Фаза0 фундамент — убрать мёртвый SimUtil, добавить LabPalette + SimBase
- Удалён _util.js (SimUtil): 0 использований во всех симуляциях (проверено), грузился впустую. - LabPalette (_palette.js): единый источник цветов canvas + PX_PER_M вместо хардкода в каждом файле; задел под светлую тему. - SimBase (_simbase.js): опциональная база жизненного цикла (DPR-fit + RAF play/pause/reset/destroy). Существующие симуляции не трогаются; «дробовик» остаётся fallback. Адаптация — постепенно, по мере правок (нет фронт-тестов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
/* SimBase — опциональная база жизненного цикла симуляции (Фаза 0).
|
||||
* Унифицирует то, что сейчас каждый sim-класс изобретает заново: DPR-fit canvas
|
||||
* и RAF-петлю play/pause + reset/destroy. Подключение — постепенное (новые и
|
||||
* рефакторимые симуляции делают `extends SimBase`); существующие не трогаются,
|
||||
* legacy-«дробовик» _pauseAllSims/closeSim остаётся как fallback, пока все не
|
||||
* мигрируют. Подкласс реализует step(dt) и/или draw().
|
||||
*
|
||||
* class MySim extends SimBase {
|
||||
* constructor(canvas){ super(canvas); ... }
|
||||
* step(dt){ ... } // физика (опц.)
|
||||
* draw(){ ... } // отрисовка
|
||||
* }
|
||||
*/
|
||||
(function (global) {
|
||||
function SimBase(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas ? canvas.getContext('2d') : null;
|
||||
this.dpr = 1;
|
||||
this.w = 0;
|
||||
this.h = 0;
|
||||
this._raf = 0;
|
||||
this._running = false;
|
||||
this._last = 0;
|
||||
}
|
||||
|
||||
SimBase.prototype.fit = function () {
|
||||
var c = this.canvas; if (!c) return;
|
||||
var dpr = Math.min(global.devicePixelRatio || 1, 2);
|
||||
var r = (c.parentElement || c).getBoundingClientRect();
|
||||
var w = Math.max(1, Math.round(r.width));
|
||||
var h = Math.max(1, Math.round(r.height));
|
||||
this.dpr = dpr; this.w = w; this.h = h;
|
||||
c.width = w * dpr; c.height = h * dpr;
|
||||
c.style.width = w + 'px'; c.style.height = h + 'px';
|
||||
if (this.ctx) this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
if (typeof this.onResize === 'function') this.onResize(w, h);
|
||||
};
|
||||
|
||||
SimBase.prototype.play = function () {
|
||||
if (this._running) return;
|
||||
this._running = true;
|
||||
this._last = (global.performance || Date).now();
|
||||
var self = this;
|
||||
function loop(t) {
|
||||
if (!self._running) return;
|
||||
var dt = Math.min((t - self._last) / 1000, 0.05); // кламп против скачков
|
||||
self._last = t;
|
||||
if (typeof self.step === 'function') self.step(dt);
|
||||
if (typeof self.draw === 'function') self.draw();
|
||||
self._raf = global.requestAnimationFrame(loop);
|
||||
}
|
||||
self._raf = global.requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
SimBase.prototype.pause = function () {
|
||||
this._running = false;
|
||||
if (this._raf) { global.cancelAnimationFrame(this._raf); this._raf = 0; }
|
||||
};
|
||||
|
||||
SimBase.prototype.isRunning = function () { return this._running; };
|
||||
|
||||
SimBase.prototype.reset = function () {
|
||||
if (typeof this.draw === 'function') this.draw();
|
||||
};
|
||||
|
||||
// Полная остановка + снятие ресурсов. Подкласс переопределяет для удаления
|
||||
// слушателей/ResizeObserver, затем вызывает super.destroy().
|
||||
SimBase.prototype.destroy = function () {
|
||||
this.pause();
|
||||
};
|
||||
|
||||
global.SimBase = SimBase;
|
||||
})(window);
|
||||
Reference in New Issue
Block a user