Skip to content

Commit d5ed188

Browse files
authored
Merge pull request #19 from LayoutitStudio/public-loading-mobile-fixes
Fix mobile controls and cold texture loading
2 parents 7d1b911 + f342179 commit d5ed188

5 files changed

Lines changed: 131 additions & 13 deletions

File tree

src/App.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,7 +2623,8 @@ function handleQuakeDebugRecordingButtonClick(event: Event): void {
26232623
function syncQuakeInteractionPresentation(): void {
26242624
const menuSurfaceOpen = menu.isMainMenuOpen() || menu.isMenuPanelOpen();
26252625
const pointerUnlocked = document.pointerLockElement !== host;
2626-
const gameplayPointerUnlocked = quakeGameplayStarted && pointerUnlocked;
2626+
const mobileControlsAvailable = quakePointerGameplay.isMobileAvailable();
2627+
const gameplayPointerUnlocked = quakeGameplayStarted && pointerUnlocked && !mobileControlsAvailable;
26272628
const debugPointerUnlocked = quakeDebugPanelFlow.isModeEnabled() && pointerUnlocked;
26282629
const clickToPlayVisible = gameplayPointerUnlocked && !menuSurfaceOpen;
26292630
setQuakeClickToPlayPauseState(clickToPlayVisible);
@@ -4394,7 +4395,7 @@ async function completeQuakeSceneReadiness(
43944395
): Promise<void> {
43954396
const completeWorldTexturesTask = progress?.startTask("World textures");
43964397
try {
4397-
await world.waitForVisibleAtlasPages();
4398+
await world.waitForVisibleTextures();
43984399
} finally {
43994400
completeWorldTexturesTask?.();
44004401
}

src/quake.css

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2398,20 +2398,21 @@ body.quake-menu-unlocked #quake-weapon {
23982398
user-select: none;
23992399
touch-action: none;
24002400
--quake-mobile-control-center-bottom: max(142px, calc(env(safe-area-inset-bottom) + 142px));
2401+
--quake-mobile-move-zone-left: max(18px, env(safe-area-inset-left));
24012402
}
24022403
#quake-mobile-look-zone {
24032404
position: absolute;
24042405
top: 0;
24052406
right: 0;
24062407
bottom: 0;
2407-
left: 34vw;
2408+
left: max(34vw, calc(var(--quake-mobile-move-zone-left) + 156px));
24082409
z-index: 0;
24092410
pointer-events: auto;
24102411
touch-action: none;
24112412
}
24122413
#quake-mobile-move-zone {
24132414
position: absolute;
2414-
left: max(18px, env(safe-area-inset-left));
2415+
left: var(--quake-mobile-move-zone-left);
24152416
bottom: calc(var(--quake-mobile-control-center-bottom) - 72px);
24162417
width: 144px;
24172418
height: 144px;

src/runtime/renderBundleMesh.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,29 @@ export async function preloadQuakeRenderBundleAtlasPages(
771771
await preloadQuakeRenderBundleAssetUrls(urls);
772772
}
773773

774+
export async function preloadQuakeRenderBundleElementAssets(
775+
meshElement: HTMLElement,
776+
elements: Iterable<HTMLElement>,
777+
): Promise<void> {
778+
const urls = quakeRenderBundleElementAssetUrls(meshElement, elements);
779+
await preloadQuakeRenderBundleAssetUrls(urls);
780+
}
781+
782+
export function quakeRenderBundleElementAssetUrls(
783+
meshElement: HTMLElement,
784+
elements: Iterable<HTMLElement>,
785+
): string[] {
786+
const urls = new Set<string>();
787+
for (const element of elements) {
788+
collectQuakeRenderBundleInlineAssetUrls(
789+
urls,
790+
element.getAttribute("style") ?? "",
791+
(varName) => meshElement.style.getPropertyValue(varName),
792+
);
793+
}
794+
return [...urls];
795+
}
796+
774797
export interface QuakeRenderBundleDebugOutlineOptions {
775798
hideTextures?: boolean;
776799
}
@@ -1263,6 +1286,31 @@ export function quakeRenderBundlePreloadAssetUrls(renderBundle: QuakePreparedRen
12631286
return [...urls];
12641287
}
12651288

1289+
function collectQuakeRenderBundleInlineAssetUrls(
1290+
urls: Set<string>,
1291+
styleText: string,
1292+
resolveVar: (name: string) => string | undefined,
1293+
): void {
1294+
if (!styleText) return;
1295+
for (const match of styleText.matchAll(/url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*?))\s*\)/g)) {
1296+
const url = normalizeQuakeRenderBundleCssUrl(match[1] ?? match[2] ?? match[3] ?? "");
1297+
if (url) urls.add(url);
1298+
}
1299+
for (const match of styleText.matchAll(/var\(\s*(--bg\d+)\s*\)/g)) {
1300+
const value = resolveVar(match[1] ?? "");
1301+
if (value) collectQuakeRenderBundleInlineAssetUrls(urls, value, resolveVar);
1302+
}
1303+
}
1304+
1305+
function normalizeQuakeRenderBundleCssUrl(value: string): string {
1306+
let url = value.trim();
1307+
if (!url) return "";
1308+
if ((url.startsWith("&quot;") && url.endsWith("&quot;")) || (url.startsWith("&#34;") && url.endsWith("&#34;"))) {
1309+
url = url.slice(url.indexOf(";") + 1, url.lastIndexOf("&"));
1310+
}
1311+
return url.trim();
1312+
}
1313+
12661314
function preloadQuakeRenderBundleStyle(renderBundle: QuakePreparedRenderBundle): Promise<void> {
12671315
const key = quakeRenderBundleStyleKey(renderBundle);
12681316
if (!key) return Promise.resolve();

src/runtime/world.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
exposeQuakeRenderBundleAtlasPages,
2626
mountQuakeRenderBundleMesh,
2727
preloadQuakeRenderBundleAtlasPages,
28+
preloadQuakeRenderBundleElementAssets,
2829
registerQuakeRenderBundleDebugLeafSourceFace,
2930
registerQuakeRenderBundleDebugOutlineLeaves,
3031
stripPolyMeshMetadata,
@@ -155,6 +156,7 @@ export interface QuakeWorldController {
155156
syncVisibility: (force?: boolean) => void;
156157
visibleLeavesAt: (origin: [number, number, number]) => Set<number> | null;
157158
waitForVisibleAtlasPages: () => Promise<void>;
159+
waitForVisibleTextures: () => Promise<void>;
158160
}
159161

160162
export interface QuakeWorldDebugBucket {
@@ -214,6 +216,8 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
214216
let visibleAtlasPageKey = "";
215217
let visibleAtlasPageSet = new Set<number>();
216218
let visibleAtlasPageReadyPromise: Promise<void> = Promise.resolve();
219+
let visibleAtlasPrewarmReadyPromise: Promise<void> = Promise.resolve();
220+
let visibleWorldTextureReadyPromise: Promise<void> = Promise.resolve();
217221
let debugShellVisible = false;
218222

219223
const clear = (): void => {
@@ -241,6 +245,8 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
241245
visibleAtlasPageKey = "";
242246
visibleAtlasPageSet = new Set();
243247
visibleAtlasPageReadyPromise = Promise.resolve();
248+
visibleAtlasPrewarmReadyPromise = Promise.resolve();
249+
visibleWorldTextureReadyPromise = Promise.resolve();
244250
};
245251

246252
const clearPresentationResyncTimers = (): void => {
@@ -1129,6 +1135,12 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
11291135
};
11301136

11311137
const waitForVisibleAtlasPages = (): Promise<void> => visibleAtlasPageReadyPromise;
1138+
const waitForVisibleTextures = (): Promise<void> =>
1139+
Promise.all([
1140+
visibleAtlasPageReadyPromise,
1141+
visibleAtlasPrewarmReadyPromise,
1142+
visibleWorldTextureReadyPromise,
1143+
]).then(() => undefined);
11321144

11331145
const syncWorldAtlasResidencyPages = (leafIndex: number | null): void => {
11341146
const atlasResidency = currentRenderBundle?.atlasResidency;
@@ -1143,7 +1155,7 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
11431155
const exposedPages = currentPages.length ? currentPages : allPages;
11441156
const warmPages = prewarmPages.length ? prewarmPages : exposedPages;
11451157
setVisibleAtlasResidencyPages(exposedPages);
1146-
void preloadQuakeRenderBundleAtlasPages(currentRenderBundle, warmPages);
1158+
visibleAtlasPrewarmReadyPromise = preloadQuakeRenderBundleAtlasPages(currentRenderBundle, warmPages);
11471159
};
11481160

11491161
const setVisibleAtlasResidencyPages = (pageIndexes: readonly number[]): void => {
@@ -1159,14 +1171,38 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
11591171
};
11601172

11611173
const syncMountedAtlasResidencyPages = (): void => {
1162-
if (!currentHandle || !currentRenderBundle || currentRenderBundle.atlasResidency?.mode !== "pvs-pages") return;
1163-
const nextPages = mountedAtlasResidencyPageIndexes(visibleAtlasPageSet);
1164-
const pageKey = nextPages.join(",");
1165-
if (pageKey === visibleAtlasPageKey) return;
1166-
exposeQuakeRenderBundleAtlasPages(currentHandle.element, currentRenderBundle, nextPages);
1167-
visibleAtlasPageSet = new Set(nextPages);
1168-
visibleAtlasPageKey = pageKey;
1169-
visibleAtlasPageReadyPromise = preloadQuakeRenderBundleAtlasPages(currentRenderBundle, nextPages);
1174+
if (!currentHandle || !currentRenderBundle) return;
1175+
if (currentRenderBundle.atlasResidency?.mode === "pvs-pages") {
1176+
const nextPages = mountedAtlasResidencyPageIndexes(visibleAtlasPageSet);
1177+
const pageKey = nextPages.join(",");
1178+
if (pageKey !== visibleAtlasPageKey) {
1179+
exposeQuakeRenderBundleAtlasPages(currentHandle.element, currentRenderBundle, nextPages);
1180+
visibleAtlasPageSet = new Set(nextPages);
1181+
visibleAtlasPageKey = pageKey;
1182+
visibleAtlasPageReadyPromise = preloadQuakeRenderBundleAtlasPages(currentRenderBundle, nextPages);
1183+
}
1184+
}
1185+
syncMountedWorldTextureReadiness();
1186+
};
1187+
1188+
const syncMountedWorldTextureReadiness = (): void => {
1189+
if (!currentHandle) {
1190+
visibleWorldTextureReadyPromise = Promise.resolve();
1191+
return;
1192+
}
1193+
visibleWorldTextureReadyPromise = preloadQuakeRenderBundleElementAssets(
1194+
currentHandle.element,
1195+
mountedWorldTextureElements(),
1196+
);
1197+
};
1198+
1199+
const mountedWorldTextureElements = (): HTMLElement[] => {
1200+
const elements: HTMLElement[] = [];
1201+
for (const leaf of quakeLeaves) {
1202+
if (leaf.meshKind !== "world" || !leaf.mounted || !leaf.element.isConnected) continue;
1203+
elements.push(leaf.element);
1204+
}
1205+
return elements;
11701206
};
11711207

11721208
const normalizedAtlasResidencyPageIndexes = (pageIndexes: Iterable<number>): number[] =>
@@ -1353,6 +1389,7 @@ export function createQuakeWorldController(options: QuakeWorldControllerOptions)
13531389
syncVisibility,
13541390
visibleLeavesAt: (origin: [number, number, number]) => currentVisibility?.visibleLeavesAt(origin) ?? null,
13551391
waitForVisibleAtlasPages,
1392+
waitForVisibleTextures,
13561393
};
13571394
}
13581395

test/runtime/renderBundlePreloadUrls.test.mjs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import assert from "node:assert/strict";
22
import test from "node:test";
33

4+
import { Window } from "happy-dom";
5+
46
import { importTsModule } from "../importTsModule.mjs";
57

68
const {
9+
quakeRenderBundleElementAssetUrls,
710
quakeRenderBundlePreloadAssetUrls,
811
} = await importTsModule("src/runtime/renderBundleMesh.ts");
912

@@ -44,3 +47,31 @@ test("render bundles without complete asset URLs fail before runtime preload", (
4447
/assetUrls must be complete/,
4548
);
4649
});
50+
51+
test("mounted render bundle leaves expose direct URLs and atlas root-var URLs for preload", () => {
52+
const window = new Window();
53+
const mesh = window.document.createElement("div");
54+
mesh.className = "polycss-mesh";
55+
mesh.style.setProperty("--bg0", 'url("/q/b/e1m1/a0.png")');
56+
57+
const directLeaf = window.document.createElement("s");
58+
directLeaf.setAttribute(
59+
"style",
60+
'background:url("/q/b/e1m1/direct.png") 0px 0px / 64px 64px no-repeat',
61+
);
62+
const atlasLeaf = window.document.createElement("s");
63+
atlasLeaf.setAttribute(
64+
"style",
65+
"background:var(--bg0) 0px 0px / 64px 64px no-repeat",
66+
);
67+
const duplicateAtlasLeaf = window.document.createElement("s");
68+
duplicateAtlasLeaf.setAttribute(
69+
"style",
70+
"background-image:var(--bg0)",
71+
);
72+
73+
assert.deepEqual(
74+
quakeRenderBundleElementAssetUrls(mesh, [directLeaf, atlasLeaf, duplicateAtlasLeaf]),
75+
["/q/b/e1m1/direct.png", "/q/b/e1m1/a0.png"],
76+
);
77+
});

0 commit comments

Comments
 (0)