Files
Learn_System/frontend/js/labs/_loader.js
T
Maxim Dolgolyov fc1139f51d feat(lab-content-engine): phase 3 - ленивая загрузка кода симуляций
Старт /lab грузит только каркас (~530KB) вместо ~2.9MB + three.js(~600KB):
- _loader.js — LabLoader.ensure(id): грузит файлы симуляции по манифесту +
  three.js при необходимости; кеш по URL; САМОВОССТАНОВЛЕНИЕ (если open-функция
  не определена после загрузки — грузит все ленивые файлы -> корректность
  гарантирована независимо от точности манифеста)
- _sim_deps.js — сгенерированный манифест SIM_DEPS{id:{open,files,three}} +
  LAB_LAZY_FILES; three:true только для crystal/orbitals/stereo/periodic
- _register-all.js — open-обёртка: LabLoader.ensure(id).then(rawOpen)
- lab-init.js openSim — обработка Promise от open() (lucide после init)
- lab.html — убраны 45 ленивых <script> + three.js из eager; каркас: registry,
  loader, sim_deps, fx-движки, общие визуалы, graph.js (GRID для 15 сим)

Проверка: vm-harness (per-sim load, three only 3D, кеш, self-heal) ALL PASS;
инвариант owner-in-files для всех 40; нет утечки ленивых в eager; node --check OK.
В БРАУЗЕРЕ НЕ ПРОВЕРЕНО.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:02:29 +03:00

77 lines
3.5 KiB
JavaScript

'use strict';
/*
* LabLoader — ленивый загрузчик кода симуляций (контент-движок, Фаза 3).
*
* Тяжёлый код симуляций (~2.5 МБ) и three.js (~600 КБ) больше НЕ грузятся на старте.
* При открытии симуляции LabLoader.ensure(id) подгружает её файлы (по манифесту
* window.SIM_DEPS из _sim_deps.js) и, при необходимости, three.js — затем резолвит.
*
* Гарантия корректности (самовосстановление): если после загрузки указанных файлов
* глобальная open-функция (SIM_DEPS[id].open, напр. "_openPendulum") всё ещё не
* определена, грузятся ВСЕ ленивые файлы (window.LAB_LAZY_FILES). Поэтому ошибка в
* манифесте не может «сломать» симуляцию — в худшем случае грузится больше файлов
* (поведение как до Фазы 3). Манифест лишь оптимизирует объём загрузки.
*
* Все загрузки кешируются (по URL) и дедуплицируются.
*/
(function () {
var BASE = '/js/labs/';
var THREE_URL = 'https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.min.js';
var _cache = {}; // url -> Promise
var _allLoaded = false;
function loadScript(url) {
if (_cache[url]) return _cache[url];
_cache[url] = new Promise(function (resolve, reject) {
var s = document.createElement('script');
s.src = url;
s.async = false; // сохранить порядок при добавлении нескольких сразу
s.onload = function () { resolve(url); };
s.onerror = function () { delete _cache[url]; reject(new Error('LabLoader: не удалось загрузить ' + url)); };
document.head.appendChild(s);
});
return _cache[url];
}
function ensureThree() {
if (typeof window.THREE !== 'undefined') return Promise.resolve();
return loadScript(THREE_URL);
}
function loadFiles(files) {
return Promise.all((files || []).map(function (f) { return loadScript(BASE + f); }));
}
function loadAllLazy() {
if (_allLoaded) return Promise.resolve();
var list = window.LAB_LAZY_FILES || [];
return loadFiles(list).then(function () { _allLoaded = true; });
}
// ensure(id): загрузить всё необходимое для симуляции id, вернуть Promise.
function ensure(id) {
var dep = (window.SIM_DEPS && window.SIM_DEPS[id]) || null;
if (!dep) {
// нет манифеста для id — безопасно грузим всё
return loadAllLazy();
}
var p = dep.three ? ensureThree() : Promise.resolve();
return p
.then(function () { return loadFiles(dep.files); })
.then(function () {
var openName = dep.open;
if (openName && typeof window[openName] !== 'function') {
if (window.console) console.warn('[LabLoader] самовосстановление для "' + id + '": ' + openName + ' не найдена после загрузки ' + JSON.stringify(dep.files) + ' — гружу все ленивые файлы');
return loadAllLazy();
}
});
}
window.LabLoader = {
ensure: ensure,
ensureThree: ensureThree,
loadScript: loadScript,
loadAllLazy: loadAllLazy
};
})();