Skip to content

Commit 3b5b811

Browse files
committed
Add fetch_resource (from Parchment), and then get it to cache resources
SoundChannelManager uses fetch_resource to allow glkaudio_bg.wasm to be loaded in single file mode
1 parent 6749d9e commit 3b5b811

File tree

8 files changed

+86
-19
lines changed

8 files changed

+86
-19
lines changed

src/common/file.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,71 @@ https://github.com/curiousdannii/asyncglk
1111

1212
export type ProgressCallback = (bytes: number) => void
1313

14+
export type TruthyOption = boolean | number
15+
16+
export interface DownloadOptions {
17+
/** Domains to access directly: should always have both Access-Control-Allow-Origin and compression headers */
18+
direct_domains: string[],
19+
/** Path to resources */
20+
lib_path: string,
21+
/** URL of Proxy */
22+
proxy_url: string,
23+
/** Whether to load embedded resources in single file mode */
24+
single_file?: TruthyOption,
25+
/** Use the file proxy; if disabled may mean that some files can't be loaded */
26+
use_proxy?: boolean | number,
27+
}
28+
29+
/** Fetch a resource */
30+
const resource_map: Map<string, any> = new Map()
31+
export async function fetch_resource(options: DownloadOptions, path: string, progress_callback?: ProgressCallback) {
32+
// Check the cache
33+
const cached = resource_map.get(path)
34+
if (cached) {
35+
return cached
36+
}
37+
38+
const response = fetch_resource_inner(options, path, progress_callback)
39+
// Fill the cache with the promise, and then when the resource has been obtained, update the cache
40+
resource_map.set(path, response)
41+
response.then((resource: any) => {
42+
resource_map.set(path, resource)
43+
})
44+
return response
45+
}
46+
47+
/** Actually fetch a resource */
48+
async function fetch_resource_inner(options: DownloadOptions, path: string, progress_callback?: ProgressCallback) {
49+
// Handle embedded resources in single file mode
50+
if (options.single_file) {
51+
const data = (document.getElementById(path) as HTMLScriptElement).text
52+
if (path.endsWith('.js')) {
53+
return import(`data:text/javascript,${encodeURIComponent(data)}`)
54+
}
55+
if (!path.endsWith('.wasm')) {
56+
throw new Error(`Can't load ${path} in single file mode`)
57+
}
58+
return parse_base64(data)
59+
}
60+
61+
// Handle when lib_path is a proper URL (such as import.meta.url), as well as the old style path fragment
62+
let url: URL | string
63+
try {
64+
url = new URL(path, options.lib_path)
65+
}
66+
catch {
67+
url = options.lib_path + path
68+
}
69+
70+
if (path.endsWith('.js')) {
71+
return import(url + '')
72+
}
73+
74+
// Something else, like a .wasm
75+
const response = await fetch(url)
76+
return read_response(response, progress_callback)
77+
}
78+
1479
/** Parse Base 64 into a Uint8Array */
1580
export async function parse_base64(data: string): Promise<Uint8Array> {
1681
// Firefox has a data URL limit of 32MB, so we have to chunk large data

src/dialog/browser/browser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ https://github.com/curiousdannii/asyncglk
1212
import {saveAs as filesave_saveAs} from 'file-saver'
1313
import path from 'path-browserify-esm'
1414

15-
import type {ProgressCallback} from '../../common/file.js'
15+
import type {DownloadOptions, ProgressCallback} from '../../common/file.js'
1616
import type {DialogDirectories, DialogOptions} from '../common/interface.js'
1717
import {show_alert} from './common.js'
1818
import {DownloadProvider, read_uploaded_file} from './download.js'
19-
import type {BrowseableProvider, BrowserDialog, DirEntry, DownloadOptions, FilesMetadata, Provider} from './interface.js'
19+
import type {BrowseableProvider, BrowserDialog, DirEntry, FilesMetadata, Provider} from './interface.js'
2020
import {WebStorageProvider} from './storage.js'
2121
import FileDialog from './ui/FileDialog.svelte'
2222

src/dialog/browser/download.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ https://github.com/curiousdannii/asyncglk
1111

1212
// The download provider stores its own files just in a map (maybe to be cached in the future), but if files are written next to them, then they need to be done so in another provider
1313

14-
import type {ProgressCallback} from '../../common/file.js'
14+
import type {DownloadOptions, ProgressCallback} from '../../common/file.js'
1515
import {NullProvider} from './common.js'
16-
import type {DownloadOptions, Provider} from './interface.js'
16+
import type {Provider} from './interface.js'
1717
import {parse_base64, read_response} from '../../common/file.js'
1818
import {utf8decoder} from '../../common/misc.js'
1919

src/dialog/browser/interface.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@ export interface BrowserDialog extends AsyncDialog {
1717
upload(file: File): Promise<string>
1818
}
1919

20-
export interface DownloadOptions {
21-
/** Domains to access directly: should always have both Access-Control-Allow-Origin and compression headers */
22-
direct_domains: string[],
23-
/** URL of Proxy */
24-
proxy_url: string,
25-
/** Disable the file proxy, which may mean that some files can't be loaded */
26-
use_proxy?: boolean | number,
27-
}
28-
2920
/** A provider handles part of the filesystem, and can cascade down to another provider for files it doesn't handle */
3021
export interface Provider {
3122
/** Whether we can browse this provider */

src/glkote/common/glkote.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ https://github.com/curiousdannii/asyncglk
1111

1212
import {Blorb} from '../../blorb/blorb.js'
1313
import * as Constants from '../../common/constants.js'
14+
import type {DownloadOptions} from '../../common/file.js'
1415
import * as protocol from '../../common/protocol.js'
1516
import {filetype_to_extension} from '../../dialog/common/common.js'
1617
import type {Dialog} from '../../dialog/common/interface.js'
@@ -34,7 +35,7 @@ export interface GlkOte {
3435
warning(msg: any): void,
3536
}
3637

37-
export interface GlkOteOptions {
38+
export interface GlkOteOptions extends DownloadOptions {
3839
accept(event: protocol.Event): void,
3940
Blorb?: Blorb,
4041
debug_commands?: boolean,
@@ -94,7 +95,7 @@ export abstract class GlkOteBase implements GlkOte {
9495
disabled = false
9596
protected generation = 0
9697
protected is_inited = false
97-
protected options: GlkOteOptions = {} as GlkOteOptions
98+
options: GlkOteOptions = {} as GlkOteOptions
9899
protected timer: ReturnType<typeof setTimeout> | null = null
99100
protected waiting_for_update = false
100101

src/glkote/web/schannels.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ https://github.com/curiousdannii/asyncglk
99
1010
*/
1111

12+
import {fetch_resource} from '../../common/file.js'
1213
import * as protocol from '../../common/protocol.js'
1314
import WebGlkOte from './web.js'
1415

@@ -17,14 +18,15 @@ import GlkAudio_init, {decode as GlkAudio_decode} from 'glkaudio'
1718
export class SoundChannelManager extends Map<number, SoundChannel> {
1819
private context: AudioContext
1920
private glkote: WebGlkOte
21+
private loaded = false
2022

2123
constructor(glkote: WebGlkOte) {
2224
super()
2325
this.glkote = glkote
2426
this.context = new AudioContext()
2527
}
2628

27-
update(schannels: protocol.SoundChannelUpdate[]) {
29+
async update(schannels: protocol.SoundChannelUpdate[]) {
2830
const wanted_schannels = []
2931
for (const schannel of schannels) {
3032
const {id, ops} = schannel
@@ -37,6 +39,12 @@ export class SoundChannelManager extends Map<number, SoundChannel> {
3739

3840
// Do operations
3941
if (ops) {
42+
// Load the glkaudio library only when we actually have something to do
43+
// We still might be loading it unnecessarily, but it's not very big
44+
if (!this.loaded) {
45+
await GlkAudio_init({module_or_path: fetch_resource(this.glkote.options, 'glkaudio_bg.wasm')})
46+
this.loaded = true
47+
}
4048
this.get(id)!.do_ops(ops)
4149
}
4250
}
@@ -112,7 +120,6 @@ export class SoundChannel {
112120
this.buffer = await context.decodeAudioData(chunk.content!.slice().buffer)
113121
}
114122
catch {
115-
await GlkAudio_init()
116123
const decoded = GlkAudio_decode(chunk.content!)
117124
this.buffer = await context.decodeAudioData(decoded.buffer)
118125
}

src/index-browser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ https://github.com/curiousdannii/asyncglk
1111

1212
export * from './index-common.js'
1313

14+
export {fetch_resource} from './common/file.js'
15+
1416
export {ProviderBasedBrowserDialog} from './dialog/browser/browser.js'
1517

1618
export {default as WebGlkOte} from './glkote/web/web.js'

src/index-common.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ export type {BlorbChunk, BlorbDataChunk, ImageInfo, ImageSize, InfoMap, InfoMapR
1414
export {IFF} from './blorb/iff.js'
1515

1616
export * as constants from './common/constants.js'
17-
export {parse_base64, type ProgressCallback, read_response} from './common/file.js'
17+
export {parse_base64, read_response} from './common/file.js'
18+
export type {DownloadOptions, ProgressCallback, TruthyOption} from './common/file.js'
1819
export {FileView} from './common/misc.js'
1920
export * as protocol from './common/protocol.js'
2021

2122
export {filetype_to_extension, filters_for_usage, path_native_to_posix, path_posix_to_native} from './dialog/common/common.js'
2223
export type {AsyncDialog, AutosaveData, ClassicFileStream, ClassicStreamingDialog, ClassicSyncDialog, Dialog, DialogDirectories, DialogOptions} from './dialog/common/interface.js'
2324

24-
export type {BrowserDialog, DownloadOptions, FileData, FilesMetadata} from './dialog/browser/interface.js'
25+
export type {BrowserDialog, FileData, FilesMetadata} from './dialog/browser/interface.js'
2526

2627
export type {GiDispa, GlkApi, GlkApiAsync, GlkApiOptions, GlkClassName, GlkFref, GlkObject, GlkSchannel, GlkStream, GlkVM, GlkWindow} from './glkapi/interface.js'
2728
export {AsyncGlk, RefBox, RefStruct} from './glkapi/glkapi.js'

0 commit comments

Comments
 (0)