Skip to content

Commit

Permalink
Add fetch_resource (from Parchment), and then get it to cache resources
Browse files Browse the repository at this point in the history
SoundChannelManager uses fetch_resource to allow glkaudio_bg.wasm to be loaded in single file mode
  • Loading branch information
curiousdannii committed Dec 10, 2024
1 parent 6749d9e commit 821d2a1
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 17 deletions.
65 changes: 65 additions & 0 deletions src/common/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,71 @@ https://github.com/curiousdannii/asyncglk

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

export type TruthyOption = boolean | number

export interface DownloadOptions {
/** Domains to access directly: should always have both Access-Control-Allow-Origin and compression headers */
direct_domains: string[],
/** Path to resources */
lib_path: string,
/** URL of Proxy */
proxy_url: string,
/** Whether to load embedded resources in single file mode */
single_file?: TruthyOption,
/** Use the file proxy; if disabled may mean that some files can't be loaded */
use_proxy?: boolean | number,
}

/** Fetch a resource */
const resource_map: Map<string, any> = new Map()
export async function fetch_resource(options: DownloadOptions, path: string, progress_callback?: ProgressCallback) {
// Check the cache
const cached = resource_map.get(path)
if (cached) {
return cached
}

const response = fetch_resource_inner(options, path, progress_callback)
// Fill the cache with the promise, and then when the resource has been obtained, update the cache
resource_map.set(path, response)
response.then((resource: any) => {
resource_map.set(path, resource)
})
return response
}

/** Actually fetch a resource */
async function fetch_resource_inner(options: DownloadOptions, path: string, progress_callback?: ProgressCallback) {
// Handle embedded resources in single file mode
if (options.single_file) {
const data = (document.getElementById(path) as HTMLScriptElement).text
if (path.endsWith('.js')) {
return import(`data:text/javascript,${encodeURIComponent(data)}`)
}
if (!path.endsWith('.wasm')) {
throw new Error(`Can't load ${path} in single file mode`)
}
return parse_base64(data)
}

// Handle when lib_path is a proper URL (such as import.meta.url), as well as the old style path fragment
let url: URL | string
try {
url = new URL(path, options.lib_path)
}
catch {
url = options.lib_path + path
}

if (path.endsWith('.js')) {
return import(url + '')
}

// Something else, like a .wasm
const response = await fetch(url)
return read_response(response, progress_callback)
}

/** Parse Base 64 into a Uint8Array */
export async function parse_base64(data: string): Promise<Uint8Array> {
// Firefox has a data URL limit of 32MB, so we have to chunk large data
Expand Down
4 changes: 2 additions & 2 deletions src/dialog/browser/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ https://github.com/curiousdannii/asyncglk

// 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

import type {ProgressCallback} from '../../common/file.js'
import type {DownloadOptions, ProgressCallback} from '../../common/file.js'
import {NullProvider} from './common.js'
import type {DownloadOptions, Provider} from './interface.js'
import type {Provider} from './interface.js'
import {parse_base64, read_response} from '../../common/file.js'
import {utf8decoder} from '../../common/misc.js'

Expand Down
9 changes: 0 additions & 9 deletions src/dialog/browser/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@ export interface BrowserDialog extends AsyncDialog {
upload(file: File): Promise<string>
}

export interface DownloadOptions {
/** Domains to access directly: should always have both Access-Control-Allow-Origin and compression headers */
direct_domains: string[],
/** URL of Proxy */
proxy_url: string,
/** Disable the file proxy, which may mean that some files can't be loaded */
use_proxy?: boolean | number,
}

/** A provider handles part of the filesystem, and can cascade down to another provider for files it doesn't handle */
export interface Provider {
/** Whether we can browse this provider */
Expand Down
5 changes: 3 additions & 2 deletions src/glkote/common/glkote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ https://github.com/curiousdannii/asyncglk

import {Blorb} from '../../blorb/blorb.js'
import * as Constants from '../../common/constants.js'
import type {DownloadOptions} from '../../common/file.js'
import * as protocol from '../../common/protocol.js'
import {filetype_to_extension} from '../../dialog/common/common.js'
import type {Dialog} from '../../dialog/common/interface.js'
Expand All @@ -34,7 +35,7 @@ export interface GlkOte {
warning(msg: any): void,
}

export interface GlkOteOptions {
export interface GlkOteOptions extends DownloadOptions {
accept(event: protocol.Event): void,
Blorb?: Blorb,
debug_commands?: boolean,
Expand Down Expand Up @@ -94,7 +95,7 @@ export abstract class GlkOteBase implements GlkOte {
disabled = false
protected generation = 0
protected is_inited = false
protected options: GlkOteOptions = {} as GlkOteOptions
options: GlkOteOptions = {} as GlkOteOptions
protected timer: ReturnType<typeof setTimeout> | null = null
protected waiting_for_update = false

Expand Down
11 changes: 9 additions & 2 deletions src/glkote/web/schannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ https://github.com/curiousdannii/asyncglk
*/

import {fetch_resource} from '../../common/file.js'
import * as protocol from '../../common/protocol.js'
import WebGlkOte from './web.js'

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

constructor(glkote: WebGlkOte) {
super()
this.glkote = glkote
this.context = new AudioContext()
}

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

// Do operations
if (ops) {
// Load the glkaudio library only when we actually have something to do
// We still might be loading it unnecessarily, but it's not very big
if (!this.loaded) {
await GlkAudio_init({module_or_path: fetch_resource(this.glkote.options, 'glkaudio_bg.wasm')})
this.loaded = true
}
this.get(id)!.do_ops(ops)
}
}
Expand Down Expand Up @@ -112,7 +120,6 @@ export class SoundChannel {
this.buffer = await context.decodeAudioData(chunk.content!.slice().buffer)
}
catch {
await GlkAudio_init()
const decoded = GlkAudio_decode(chunk.content!)
this.buffer = await context.decodeAudioData(decoded.buffer)
}
Expand Down
2 changes: 2 additions & 0 deletions src/index-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ https://github.com/curiousdannii/asyncglk

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

export {fetch_resource} from './common/file.js'

export {ProviderBasedBrowserDialog} from './dialog/browser/browser.js'

export {default as WebGlkOte} from './glkote/web/web.js'
5 changes: 3 additions & 2 deletions src/index-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ export type {BlorbChunk, BlorbDataChunk, ImageInfo, ImageSize, InfoMap, InfoMapR
export {IFF} from './blorb/iff.js'

export * as constants from './common/constants.js'
export {parse_base64, type ProgressCallback, read_response} from './common/file.js'
export {parse_base64, read_response} from './common/file.js'
export type {DownloadOptions, ProgressCallback, TruthyOption} from './common/file.js'
export {FileView} from './common/misc.js'
export * as protocol from './common/protocol.js'

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

export type {BrowserDialog, DownloadOptions, FileData, FilesMetadata} from './dialog/browser/interface.js'
export type {BrowserDialog, FileData, FilesMetadata} from './dialog/browser/interface.js'

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

0 comments on commit 821d2a1

Please sign in to comment.