Skip to content

Commit 2a2e544

Browse files
committed
Add the ability to prefetch CD-ROM chunks
Sends them to the service worker using the same mechanism as regular disk images. Updates #382
1 parent 6410ac5 commit 2a2e544

9 files changed

Lines changed: 87 additions & 30 deletions

src/Browser.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ function DiskContents({disk, onRun}: DiskContentsProps) {
322322
libraryDownloadURLs: [],
323323
machine: disk.preferredMachine,
324324
cdromURLs: [],
325+
cdromPrefetchChunks: [],
325326
diskFiles: [],
326327
} satisfies RunDef;
327328
const inNewWindow =

src/Custom.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function Custom({
3333
screenSize: "auto",
3434
disks: [defaultDisk],
3535
cdromURLs: [],
36+
cdromPrefetchChunks: [],
3637
includeInfiniteHD: true,
3738
includeSavedHD: canSaveDisks(),
3839
includeLibrary: true,

src/Embed.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function Embed({
3838
screenSize: "auto",
3939
disks: [defaultDisk],
4040
cdromURLs: [],
41+
cdromPrefetchChunks: [],
4142
includeInfiniteHD: true,
4243
includeSavedHD: false,
4344
includeLibrary: false,

src/RunDefMac.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@ export default function RunDefMac({runDef, onDone}: RunDefMacProps) {
1818
);
1919
useEffect(() => {
2020
if (runDef.cdromURLs.length) {
21-
Promise.all(runDef.cdromURLs.map(getCDROMInfo))
21+
Promise.all(
22+
runDef.cdromURLs.map(async (url, i) => {
23+
const info = await getCDROMInfo(url);
24+
const prefetchChunks = runDef.cdromPrefetchChunks[i];
25+
if (prefetchChunks?.length > 0) {
26+
return {...info, prefetchChunks};
27+
}
28+
return info;
29+
})
30+
)
2231
.then(setCDROMs)
2332
.catch(error => {
2433
setCDROMErrorText(`Could not load the CD-ROM: ${error}`);
2534
setCDROMs([]);
2635
});
2736
}
28-
}, [runDef.cdromURLs]);
37+
}, [runDef.cdromURLs, runDef.cdromPrefetchChunks]);
2938
const disks = Array.from(new Set(runDef.disks));
3039
disks.forEach(disk => disk.generatedSpec()); // Prefetch the disk definition
3140
return cdroms ? (

src/emulator/emulator-common.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ export function generateNextChunkUrl(
121121
url: string,
122122
specs: EmulatorChunkedFileSpec[]
123123
): string | undefined {
124+
if (url.includes("CD-ROM")) {
125+
return undefined;
126+
}
124127
const match = url.match(/.*\/([0-9a-f]+)\.chunk#(\d+)$/);
125128
if (!match) {
126129
console.warn(`Could not parse chunk URL ${url}`);
@@ -509,6 +512,38 @@ export type EmulatorCDROM = {
509512
platform?: "Macintosh" | "NeXT";
510513
fetchClientSide?: boolean;
511514
mountReadWrite?: boolean;
515+
prefetchChunks?: number[];
512516
};
513517

514518
export type EmulatorCDROMLibrary = {[path: string]: EmulatorCDROM};
519+
520+
export function generateChunkedFileSpecForCDROM(
521+
cdrom: EmulatorCDROM
522+
): EmulatorChunkedFileSpec {
523+
const CHUNK_SIZE = 128 * 1024;
524+
525+
const {name, fileSize: totalSize} = cdrom;
526+
let chunkStart = 0;
527+
const chunks: string[] = [];
528+
while (chunkStart < totalSize) {
529+
const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, totalSize);
530+
chunks.push(`${chunkStart}-${chunkEnd}`);
531+
chunkStart = chunkEnd;
532+
}
533+
// Minimal metadata for workers-site/cd-rom.ts to reconstruct
534+
const encoded = btoa(
535+
JSON.stringify({
536+
srcUrl: cdrom.srcUrl,
537+
totalSize,
538+
})
539+
);
540+
541+
return {
542+
name,
543+
baseUrl: `/CD-ROM/${encoded}`,
544+
totalSize,
545+
chunks,
546+
chunkSize: CHUNK_SIZE,
547+
prefetchChunks: cdrom.prefetchChunks ?? [0],
548+
};
549+
}

src/emulator/emulator-ui.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type EmulatorWorkerConfig,
77
type EmulatorWorkerVideoBlit,
88
type EmulatorMouseEvent,
9+
generateChunkedFileSpecForCDROM,
910
} from "./emulator-common";
1011
import {
1112
emulatorUsesPlaceholderDisks,
@@ -462,7 +463,18 @@ export class Emulator {
462463

463464
const serviceWorkerAvailable = await this.#serviceWorkerReady;
464465
if (serviceWorkerAvailable) {
465-
for (const spec of config.disks.concat(config.delayedDisks ?? [])) {
466+
const specs = [
467+
...config.disks,
468+
...(config.delayedDisks ?? []),
469+
...config.cdroms
470+
.filter(
471+
cdrom =>
472+
!cdrom.fetchClientSide &&
473+
cdrom.prefetchChunks?.length
474+
)
475+
.map(generateChunkedFileSpecForCDROM),
476+
];
477+
for (const spec of specs) {
466478
this.#serviceWorker!.postMessage({
467479
type: "init-disk-cache",
468480
spec,
@@ -788,7 +800,6 @@ export class Emulator {
788800

789801
#getAdbKeyCode(code: string): number | undefined {
790802
if (this.#delegate?.emulatorSettings?.(this).swapControlAndCommand) {
791-
console.log("swapping control and command keys");
792803
if (code.startsWith("Control")) {
793804
code = "Meta" + code.slice("Control".length);
794805
} else if (code.startsWith("Meta")) {

src/emulator/emulator-worker-cdrom-disk.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import {type EmulatorCDROM} from "./emulator-common";
1+
import {
2+
type EmulatorCDROM,
3+
generateChunkedFileSpecForCDROM,
4+
} from "./emulator-common";
25
import {
36
EmulatorWorkerChunkedDisk,
47
type EmulatorWorkerChunkedDiskDelegate,
58
} from "./emulator-worker-chunked-disk";
69
import {type EmulatorWorkerDisk} from "./emulator-worker-disks";
710
import {EmulatorWorkerUploadDisk} from "./emulator-worker-upload-disk";
811

9-
const CHUNK_SIZE = 128 * 1024;
10-
1112
export function createEmulatorWorkerCDROMDisk(
1213
cdrom: EmulatorCDROM,
1314
delegate: EmulatorWorkerChunkedDiskDelegate
@@ -22,29 +23,9 @@ export function createEmulatorWorkerCDROMDisk(
2223
delegate
2324
);
2425
}
25-
const {name, fileSize: totalSize} = cdrom;
26-
let chunkStart = 0;
27-
const chunks: string[] = [];
28-
while (chunkStart < totalSize) {
29-
const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, totalSize);
30-
chunks.push(`${chunkStart}-${chunkEnd}`);
31-
chunkStart = chunkEnd;
32-
}
33-
// Minimal metadata for workers-site/cd-rom.ts to reconstruct
34-
const encoded = btoa(
35-
JSON.stringify({
36-
srcUrl: cdrom.srcUrl,
37-
totalSize,
38-
})
39-
);
40-
const spec = {
41-
name,
42-
baseUrl: `/CD-ROM/${encoded}`,
43-
totalSize,
44-
chunks,
45-
chunkSize: CHUNK_SIZE,
46-
prefetchChunks: [0],
47-
};
26+
27+
const spec = generateChunkedFileSpecForCDROM(cdrom);
28+
4829
let disk: EmulatorWorkerDisk = new EmulatorWorkerChunkedDisk(
4930
spec,
5031
delegate

src/emulator/emulator-worker-disks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export class EmulatorWorkerDisksApi {
3636
this.#removableDisks.push(new EmulatorRemovableDisk());
3737
}
3838
}
39+
// Expose the validation function globally in case the user wants to
40+
// re-validate prefetched chunks even later.
41+
(globalThis as any)["validateDisks"] = this.validate.bind(this);
3942
}
4043

4144
isMediaPresent(diskId: DiskId): boolean {

src/run-def.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type RunDef = {
2828
disks: SystemDiskDef[];
2929
diskFiles: DiskFile[];
3030
cdromURLs: string[];
31+
cdromPrefetchChunks: number[][];
3132
includeInfiniteHD: boolean;
3233
includeSavedHD: boolean;
3334
includeLibrary: boolean;
@@ -149,6 +150,16 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
149150
...searchParams.getAll("cdrom"),
150151
...searchParams.getAll("cdrom_url"),
151152
];
153+
const cdromPrefetchChunks: number[][] = [];
154+
for (const cdromPrefetch of searchParams.getAll("cdrom_prefetch")) {
155+
const chunks = cdromPrefetch
156+
.split(",")
157+
.map(s => parseInt(s))
158+
.filter(n => !isNaN(n));
159+
if (chunks.length > 0) {
160+
cdromPrefetchChunks.push(chunks);
161+
}
162+
}
152163

153164
let ethernetProvider;
154165
const appleTalkZoneName = searchParams.get("appleTalk");
@@ -197,6 +208,7 @@ export function runDefFromUrl(urlString: string): RunDef | undefined {
197208
screenScale,
198209
ethernetProvider,
199210
cdromURLs,
211+
cdromPrefetchChunks,
200212
diskFiles: [],
201213
customDate,
202214
debugFallback,
@@ -250,6 +262,9 @@ export function runDefToUrl(runDef: RunDef, toEmbed: boolean = false): string {
250262
for (const cdromURL of runDef.cdromURLs) {
251263
url.searchParams.append("cdrom", cdromURL);
252264
}
265+
for (const cdromPrefetch of runDef.cdromPrefetchChunks) {
266+
url.searchParams.append("cdrom_prefetch", cdromPrefetch.join(","));
267+
}
253268

254269
if (
255270
toEmbed ||

0 commit comments

Comments
 (0)