feat(rbxl): GUI mode + предупреждение про большие карты
Robloxity (20402 Part, 278 скриптов, 295 BillboardGui, 0.1 FPS) показал:
1. Большие карты могут зависнуть студию навсегда.
2. BillboardGui/SurfaceGui (вывески, табло) рендерятся в 3D-сцене и
при 200+ штук убивают FPS.
Фиксы:
1. Предупреждение в модалке если parts > 5000 (жёлтое) или > 15000
(красное "может зависнуть"). Подсказка про режимы.
2. Новая опция guiMode (показывается если GUI > 50 элементов):
- 'all' — все, как было.
- 'screen-only' (рекомендуется) — только ScreenGui HUD,
BillboardGui/SurfaceGui удаляются.
- 'skip' — без GUI совсем.
3. converter.py: маркирует элемент полем gui_container_kind:
'screen' / 'billboard' / 'surface'.
4. app.py: _apply_gui_mode() фильтрует scene.gui[] по режиму.
Deploy app.py + converter.py на VM 130.
Robloxity рекомендуем импортировать со screen-only — Карта Robloxity
будет работать в 5-10× быстрее без вывесок города.
This commit is contained in:
parent
a16c819726
commit
b09dd703af
Binary file not shown.
@ -216,6 +216,10 @@ def create():
|
||||
scripts_mode = data.get('scripts_mode', 'disabled')
|
||||
if scripts_mode not in ('disabled', 'enabled', 'skip'):
|
||||
scripts_mode = 'disabled'
|
||||
# gui_mode: 'all' / 'screen-only' (только ScreenGui-HUD) / 'skip' (без GUI)
|
||||
gui_mode = data.get('gui_mode', 'all')
|
||||
if gui_mode not in ('all', 'screen-only', 'skip'):
|
||||
gui_mode = 'all'
|
||||
|
||||
if not preview_hash:
|
||||
return jsonify({'error': 'preview_hash required'}), 400
|
||||
@ -284,6 +288,10 @@ def create():
|
||||
# либо удаляем все скрипты полностью.
|
||||
_apply_scripts_mode(project_data, scripts_mode)
|
||||
|
||||
# Применяем gui_mode: удаляем 3D-GUI (BillboardGui/SurfaceGui) или вообще
|
||||
# всё, если выбрано 'skip'.
|
||||
_apply_gui_mode(project_data, gui_mode)
|
||||
|
||||
# Создаём проект в kubikon3d_projects
|
||||
# Используем bridge на user-service / storys API, ИЛИ напрямую в storys_db.
|
||||
# Прямой INSERT — проще для MVP. id автогенерируется.
|
||||
@ -345,6 +353,26 @@ def _resolve_asset_urls(project_data: dict, asset_map: dict) -> None:
|
||||
snd['url'] = asset_map[rid]
|
||||
|
||||
|
||||
def _apply_gui_mode(project_data: dict, mode: str) -> None:
|
||||
"""Фильтрует scene.gui[] по режиму.
|
||||
|
||||
'all' — оставить всё (default).
|
||||
'screen-only' — оставить только ScreenGui-HUD, удалить billboard/surface.
|
||||
Карты с 200+ BillboardGui (Robloxity) перестают тормозить.
|
||||
'skip' — удалить gui[] совсем.
|
||||
"""
|
||||
scene = project_data.get('scene', {})
|
||||
if mode == 'skip':
|
||||
scene['gui'] = []
|
||||
return
|
||||
if mode == 'screen-only':
|
||||
gui = scene.get('gui', [])
|
||||
scene['gui'] = [g for g in gui
|
||||
if g.get('gui_container_kind', 'screen') == 'screen']
|
||||
return
|
||||
# 'all' — без изменений
|
||||
|
||||
|
||||
def _apply_scripts_mode(project_data: dict, mode: str) -> None:
|
||||
"""Применяет режим scripts_mode к проекту.
|
||||
|
||||
|
||||
@ -825,9 +825,13 @@ class Converter:
|
||||
if not hasattr(self, '_screen_gui_refs'):
|
||||
self._screen_gui_refs = set()
|
||||
self._screen_gui_enabled = {}
|
||||
self._screen_gui_kind = {} # ref → 'screen' | 'billboard' | 'surface'
|
||||
self._screen_gui_refs.add(inst.referent)
|
||||
enabled = inst.properties.get('Enabled', True)
|
||||
self._screen_gui_enabled[inst.referent] = bool(enabled) if enabled is not None else True
|
||||
# Сохраняем тип контейнера — потом отфильтруем 3D-GUI если выбрано screen-only
|
||||
kind = {'ScreenGui': 'screen', 'BillboardGui': 'billboard', 'SurfaceGui': 'surface'}.get(inst.class_name, 'screen')
|
||||
self._screen_gui_kind[inst.referent] = kind
|
||||
|
||||
def _gui_parent_id(self, parent_ref) -> Optional[str]:
|
||||
if parent_ref is None:
|
||||
@ -921,12 +925,14 @@ class Converter:
|
||||
# элемент тоже невидим.
|
||||
parent_ref = inst.parent_referent
|
||||
screen_enabled = True
|
||||
container_kind = 'screen' # default
|
||||
if hasattr(self, '_screen_gui_refs'):
|
||||
cur = parent_ref
|
||||
depth = 0
|
||||
while cur is not None and depth < 50:
|
||||
if cur in self._screen_gui_refs:
|
||||
screen_enabled = self._screen_gui_enabled.get(cur, True)
|
||||
container_kind = self._screen_gui_kind.get(cur, 'screen')
|
||||
break
|
||||
# Поиск родителя cur в instances (если есть)
|
||||
cur_inst = self.model.by_referent.get(cur) if hasattr(self, 'model') else None
|
||||
@ -979,6 +985,10 @@ class Converter:
|
||||
'imageAsset': None,
|
||||
'zIndex': int(props.get('ZIndex', 1) or 1),
|
||||
'origin': 'roblox-' + cls.lower(),
|
||||
# 'screen' — обычный HUD; 'billboard' — 3D-табличка над частью;
|
||||
# 'surface' — на грани Part. Last 2 рендерятся в 3D-сцене и
|
||||
# сильно тормозят если их сотни.
|
||||
'gui_container_kind': container_kind,
|
||||
}
|
||||
scene['gui'].append(element)
|
||||
|
||||
|
||||
@ -63,6 +63,8 @@ export async function createRbxlProject(previewHash, title, opts = {}) {
|
||||
// 'enabled' — попытаться запустить (может вешать карту)
|
||||
// 'skip' — не импортировать совсем
|
||||
scripts_mode: opts.scriptsMode || 'disabled',
|
||||
// 'all' (default) / 'screen-only' (только HUD) / 'skip' (без GUI)
|
||||
gui_mode: opts.guiMode || 'all',
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
|
||||
@ -29,6 +29,10 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
// Режим скриптов: 'disabled' (импортнуть выключенными — для чтения),
|
||||
// 'enabled' (попытаться запустить — может вешать карту), 'skip' (удалить).
|
||||
const [scriptsMode, setScriptsMode] = useState('disabled');
|
||||
// Режим GUI: 'all' — все, 'screen-only' — только ScreenGui (HUD),
|
||||
// 'skip' — не импортировать. Старые карты часто имеют 200+ BillboardGui
|
||||
// (вывески города), что вешает рендер.
|
||||
const [guiMode, setGuiMode] = useState('all');
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
if (!open) return null;
|
||||
@ -49,6 +53,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
setFile(null); setReport(null); setPreviewHash(null);
|
||||
setTitle(''); setError(null); setAnalyzing(false); setCreating(false);
|
||||
setScriptsMode('disabled');
|
||||
setGuiMode('all');
|
||||
};
|
||||
|
||||
const handleClose = () => { reset(); onClose?.(); };
|
||||
@ -92,7 +97,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
setCreating(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await createRbxlProject(previewHash, title, { scriptsMode });
|
||||
const result = await createRbxlProject(previewHash, title, { scriptsMode, guiMode });
|
||||
onCreated?.(result);
|
||||
handleClose();
|
||||
// редирект на редактор
|
||||
@ -179,6 +184,29 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{report.primitives_created > 5000 && (
|
||||
<div style={{
|
||||
marginTop: 12, padding: 12,
|
||||
background: report.primitives_created > 15000 ? '#5a1a1a' : '#4a3a1a',
|
||||
borderRadius: 6,
|
||||
border: '1px solid ' + (report.primitives_created > 15000 ? '#a55' : '#a85'),
|
||||
}}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>
|
||||
{report.primitives_created > 15000
|
||||
? '🛑 Очень большая карта'
|
||||
: '⚠️ Большая карта'}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: '#ddd' }}>
|
||||
{report.primitives_created} Part'ов — это много. Студия может
|
||||
{report.primitives_created > 15000
|
||||
? ' зависнуть или работать с FPS < 1.'
|
||||
: ' тормозить (FPS 10-30).'}
|
||||
{' '}Рекомендуем выбрать ниже «Не импортировать скрипты»
|
||||
чтобы хоть посмотреть геометрию.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{report.top_classes?.length > 0 && (
|
||||
<details style={{ marginTop: 12 }}>
|
||||
<summary style={{ cursor: 'pointer' }}>Что внутри (топ-25 классов)</summary>
|
||||
@ -262,6 +290,46 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(() => {
|
||||
const guiCount = (report.top_classes || [])
|
||||
.filter(c => /Gui|Frame|Label|Button|Image|Text/.test(c.class))
|
||||
.reduce((s, c) => s + c.count, 0);
|
||||
if (guiCount < 50) return null;
|
||||
return (
|
||||
<div style={{ marginTop: 16, padding: 12, background: '#1f1f1f', borderRadius: 6, border: '1px solid #333' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8 }}>
|
||||
Что делать с GUI ({guiCount}+ элементов)?
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: '#888', marginBottom: 8 }}>
|
||||
В этой карте много GUI-элементов (BillboardGui — вывески, табло).
|
||||
Они сильно тормозят рендер если их сотни.
|
||||
</div>
|
||||
{['all', 'screen-only', 'skip'].map((m) => (
|
||||
<label key={m} style={{ display: 'flex', alignItems: 'flex-start', gap: 8, padding: '6px 0', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio" name="guiMode" value={m}
|
||||
checked={guiMode === m}
|
||||
onChange={() => setGuiMode(m)}
|
||||
style={{ marginTop: 3 }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontSize: 13 }}>
|
||||
{m === 'all' && 'Все GUI'}
|
||||
{m === 'screen-only' && (<>Только <b>ScreenGui</b> (рекомендуется)</>)}
|
||||
{m === 'skip' && 'Без GUI вообще'}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: '#888' }}>
|
||||
{m === 'all' && 'Может тормозить.'}
|
||||
{m === 'screen-only' && 'HUD остаётся, BillboardGui/SurfaceGui (3D-вывески) удаляются.'}
|
||||
{m === 'skip' && 'Самый быстрый рендер. Только геометрия мира.'}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<label style={{ display: 'block', marginBottom: 6 }}>Название игры:</label>
|
||||
<input
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user