Skip to content

Commit b04d2f5

Browse files
committed
chore: update types loader-cache
1 parent 7866353 commit b04d2f5

4 files changed

Lines changed: 105 additions & 95 deletions

File tree

src/data-structure/loader-cache.ts

Lines changed: 56 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getOwner, onCleanup } from "solid-js"
22
import type { Loader } from "three"
3-
import type { LoaderUrl } from "../types.ts"
4-
import { load, type LoadOutput } from "../utils.ts"
3+
import type { LoaderData, LoaderUrl, PromiseMaybe } from "../types.ts"
4+
import { isRecord } from "../utils.ts"
55
import { TreeRegistry } from "./tree-registry.ts"
66

77
/**********************************************************************************/
@@ -20,10 +20,10 @@ export interface LoaderRegistry {
2020
* @param url The URL or path to the resource
2121
* @param data The resource promise or resolved data
2222
*/
23-
set<TData extends object, TUrl extends string | string[]>(
24-
loader: Loader<TData, TUrl>,
25-
url: TUrl,
26-
data: Promise<TData>,
23+
set<TLoader extends Loader<object, any>>(
24+
loader: TLoader,
25+
url: LoaderUrl<TLoader>,
26+
data: PromiseMaybe<LoaderData<TLoader>>,
2727
): void
2828

2929
/**
@@ -32,54 +32,34 @@ export interface LoaderRegistry {
3232
* @param url The URL or path to the resource
3333
* @returns The resource promise, resolved data, or undefined if not found
3434
*/
35-
get<TData extends object, TUrl extends string | string[]>(
36-
loader: Loader<TData, TUrl>,
37-
url: TUrl,
35+
get<TLoader extends Loader<object, any>>(
36+
loader: TLoader,
37+
url: LoaderUrl<TLoader>,
3838
warn?: boolean,
39-
): Promise<TData> | TData | undefined
39+
): PromiseMaybe<LoaderData<TLoader>> | undefined
4040
}
4141

4242
/**********************************************************************************/
4343
/* */
44-
/* Utils */
44+
/* Loader Registry */
4545
/* */
4646
/**********************************************************************************/
4747

48-
/**
49-
* Gets a resource from cache or loads and caches it.
50-
* @param registry The cache registry to use
51-
* @param loader The Three.js loader instance
52-
* @param url The URL(s) to load
53-
* @returns Promise resolving to the loaded resource
54-
* @internal
55-
*/
56-
export async function getOrInsertLoaderRegistry<
57-
TLoader extends Loader<any, any>,
58-
TInput extends LoaderUrl<TLoader> | Record<string, LoaderUrl<TLoader>>,
59-
>(registry: LoaderRegistry, loader: TLoader, url: TInput): Promise<LoadOutput<TLoader, TInput>> {
60-
const cachedPromise = registry.get(loader, url, false)
61-
62-
if (cachedPromise) {
63-
return cachedPromise
64-
}
65-
66-
const promise = load(loader, url)
67-
registry.set(loader, url, promise)
68-
69-
return promise
48+
interface LoaderTreeRegistryMap extends Map<Loader<any, any>, any> {
49+
get<TLoader extends Loader<object, any>>(loader: TLoader): LoaderTreeRegistry<TLoader> | undefined
50+
set<TLoader extends Loader<object, any>>(loader: TLoader, data: LoaderTreeRegistry<TLoader>): this
7051
}
7152

72-
/**********************************************************************************/
73-
/* */
74-
/* Loader Registry */
75-
/* */
76-
/**********************************************************************************/
53+
interface LoaderTreeRegistry<TLoader extends Loader<object, any>> extends TreeRegistry<object> {
54+
get(paths: LoaderUrl<TLoader>, warn?: boolean): CacheNode<TLoader>
55+
set(paths: LoaderUrl<TLoader>, data: CacheNode<TLoader>): void
56+
}
7757

7858
export class LoaderCache implements LoaderRegistry {
7959
/** Map of loader instances to their respective tree registries */
80-
#registryMap = new Map<Loader<any, any>, TreeRegistry<CacheNode<object>>>()
60+
#treeRegistryMap: LoaderTreeRegistryMap = new Map() as unknown as LoaderTreeRegistryMap
8161
/** Weak map for reverse lookup from data to cache nodes */
82-
#dataMap = new WeakMap<object, CacheNode<object>>()
62+
#dataMap = new WeakMap<object, CacheNode<Loader<any, any>>>()
8363

8464
/** Set of resources that do not have active references and can be safely cleaned up. */
8565
freeList = new Set<object>()
@@ -90,12 +70,15 @@ export class LoaderCache implements LoaderRegistry {
9070
* @returns The tree registry for this loader
9171
* @private
9272
*/
93-
#registry<T extends object>(loader: Loader<T, any>) {
94-
let registry = this.#registryMap.get(loader)
73+
#registry<TLoader extends Loader<object, any>>(loader: TLoader) {
74+
let registry = this.#treeRegistryMap.get(loader)
9575
if (!registry) {
96-
this.#registryMap.set(loader, (registry = new TreeRegistry()))
76+
this.#treeRegistryMap.set(
77+
loader,
78+
(registry = new TreeRegistry() as LoaderTreeRegistry<TLoader>),
79+
)
9780
}
98-
return registry as TreeRegistry<CacheNode<T>>
81+
return registry
9982
}
10083

10184
/**
@@ -104,7 +87,7 @@ export class LoaderCache implements LoaderRegistry {
10487
* @param options.force Force deletion even if not in free list
10588
* @private
10689
*/
107-
#delete(node: CacheNode<any>, { force }: { force?: boolean } = {}) {
90+
#delete(node: CacheNode<Loader<any, any>>, { force }: { force?: boolean } = {}) {
10891
if (!force && !this.freeList.has(node.data)) {
10992
console.error(
11093
`Attempting to delete a non-freed resource. Use { force: true } if you are sure you want to dispose`,
@@ -143,18 +126,18 @@ export class LoaderCache implements LoaderRegistry {
143126
/**
144127
* Removes a resource from a specific loader's cache at the given path.
145128
* @param loader The Three.js loader instance
146-
* @param path The URL or path to the resource
129+
* @param url The URL or path to the resource
147130
* @param options.force Force deletion even if resource has active references
148131
*/
149-
delete<TData extends object, TUrl extends string | string[]>(
150-
loader: Loader<TData, TUrl>,
151-
path: TUrl,
132+
delete<TLoader extends Loader<object, any>>(
133+
loader: TLoader,
134+
url: LoaderUrl<TLoader>,
152135
options?: { force?: boolean },
153136
) {
154-
const node = this.#registry(loader).get(path)
137+
const node = this.#registry(loader)?.get(url)
155138

156139
if (!node) {
157-
console.error(`Error while deleting path ${path}. Could not find CacheNode.`)
140+
console.error(`Error while deleting path ${url}. Could not find CacheNode.`)
158141
return
159142
}
160143

@@ -165,19 +148,18 @@ export class LoaderCache implements LoaderRegistry {
165148
* Retrieves a resource from the cache and tracks its usage.
166149
* Automatically integrates with Solid.js cleanup for reference counting.
167150
* @param loader The Three.js loader instance
168-
* @param path The URL or path to the resource
151+
* @param url The URL or path to the resource
169152
* @returns The resource promise, resolved data, or undefined if not found
170153
*/
171-
get<TData extends object, TUrl extends string | string[]>(
172-
loader: Loader<TData, TUrl>,
173-
path: TUrl,
174-
warn = true,
175-
) {
176-
const node = this.#registry(loader).get(path, warn)
154+
get<TLoader extends Loader<object, any>>(
155+
loader: TLoader,
156+
url: LoaderUrl<TLoader>,
157+
warn?: boolean,
158+
): PromiseMaybe<LoaderData<TLoader>> | undefined {
159+
const node = this.#registry(loader)?.get(url, warn)
177160

178161
if (!node) return undefined
179162

180-
node.track()
181163
return node.data
182164
}
183165

@@ -189,14 +171,14 @@ export class LoaderCache implements LoaderRegistry {
189171
* @param options.force Force update even if resource already exists
190172
* @returns The stored promise
191173
*/
192-
set<TData extends object, TUrl extends string | string[]>(
193-
loader: Loader<TData, TUrl>,
194-
path: TUrl,
195-
data: Promise<TData>,
174+
set<TLoader extends Loader<object, any>>(
175+
loader: TLoader,
176+
path: LoaderUrl<TLoader>,
177+
data: PromiseMaybe<LoaderData<TLoader>>,
196178
options?: { force?: boolean },
197179
) {
198180
const registry = this.#registry(loader)
199-
let node = registry.get(path, false)
181+
let node = registry?.get(path, false)
200182

201183
if (node) {
202184
node.update(data, options)
@@ -215,11 +197,11 @@ export class LoaderCache implements LoaderRegistry {
215197
* @template T The type of Three.js resource (must be an object)
216198
* @internal
217199
*/
218-
class CacheNode<T extends object> {
200+
class CacheNode<TLoader extends Loader<any, any>> {
219201
/** Reference count for this resource */
220202
count = 0
221203
/** The cached resource (promise or resolved value) */
222-
data: Promise<T> | T
204+
data: PromiseMaybe<LoaderData<TLoader>>
223205

224206
/**
225207
* Creates a new cache node.
@@ -230,9 +212,9 @@ class CacheNode<T extends object> {
230212
*/
231213
constructor(
232214
public free: Set<object>,
233-
public registry: TreeRegistry<CacheNode<T>>,
234-
public path: string | string[],
235-
promise: Promise<T>,
215+
public registry: LoaderTreeRegistry<TLoader>,
216+
public path: LoaderUrl<TLoader>,
217+
promise: PromiseMaybe<LoaderData<TLoader>>,
236218
) {
237219
this.data = promise
238220
this.#set(promise)
@@ -243,9 +225,9 @@ class CacheNode<T extends object> {
243225
* @param promise The resource promise
244226
* @private
245227
*/
246-
#set(promise: Promise<T>) {
228+
#set(promise: PromiseMaybe<LoaderData<TLoader>>) {
247229
this.data = promise
248-
promise.then(value => {
230+
Promise.resolve(promise).then(value => {
249231
// Update data to resolved promise, if it hasn't been updated before
250232
if (this.data === promise) {
251233
this.data = value
@@ -258,8 +240,8 @@ class CacheNode<T extends object> {
258240
* @private
259241
*/
260242
#dispose() {
261-
if ("dispose" in this.data && typeof this.data.dispose === "function") {
262-
this.data.dispose?.()
243+
if (isRecord(this.data) && "dispose" in this.data && typeof this.data.dispose === "function") {
244+
this.data.dispose()
263245
}
264246
}
265247

@@ -302,7 +284,7 @@ class CacheNode<T extends object> {
302284
* @param data The new resource promise
303285
* @param options.force Force update and dispose current resource (default: true)
304286
*/
305-
update(data: Promise<T>, { force = true }: { force?: boolean } = {}) {
287+
update(data: PromiseMaybe<LoaderData<TLoader>>, { force = true }: { force?: boolean } = {}) {
306288
if (this.data !== data && !force) {
307289
console.error(
308290
"Attempted to update already set resource. To overwrite and dispose of current resource, use { force: true } instead.",

src/data-structure/tree-registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export class TreeRegistry<T> implements TreeBase<T> {
101101
return undefined
102102
}
103103

104-
return node?.data
104+
return node.data
105105
}
106106

107107
/**

src/hooks.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import {
88
useContext,
99
} from "solid-js"
1010
import { type Loader } from "three"
11-
import {
12-
getOrInsertLoaderRegistry,
13-
LoaderCache,
14-
type LoaderRegistry,
15-
} from "./data-structure/loader-cache.ts"
16-
import type { AccessorMaybe, Constructor, Context, FrameListener, LoaderUrl } from "./types.ts"
11+
import { LoaderCache, type LoaderRegistry } from "./data-structure/loader-cache.ts"
12+
import type {
13+
AccessorMaybe,
14+
Constructor,
15+
Context,
16+
FrameListener,
17+
LoaderUrl,
18+
PromiseMaybe,
19+
} from "./types.ts"
1720
import {
1821
awaitMapObject,
1922
isRecord,
@@ -199,17 +202,52 @@ export function useLoader<
199202
return loader
200203
})
201204

202-
function loadUrl(url: LoaderUrl<TLoader>) {
205+
/**
206+
* Gets a resource from cache or loads and caches it.
207+
* @param registry The cache registry to use
208+
* @param loader The Three.js loader instance
209+
* @param input The URL(s) to load
210+
* @returns Promise resolving to the loaded resource
211+
* @internal
212+
*/
213+
function getOrInsert<TLoader extends Loader<any, any>, TInput extends LoadInput<TLoader>>(
214+
registry: LoaderRegistry,
215+
loader: TLoader,
216+
input: TInput,
217+
): PromiseMaybe<LoadOutput<TLoader, TInput>> {
218+
if (isRecord(input)) {
219+
return awaitMapObject(input, value => getOrInsert(registry, loader, value)) as Promise<
220+
LoadOutput<TLoader, TInput>
221+
>
222+
} else {
223+
const _input = input as LoaderUrl<TLoader>
224+
225+
const cachedPromise = registry.get(loader, _input, false)
226+
227+
if (cachedPromise) {
228+
return cachedPromise
229+
}
230+
231+
const promise = load(loader, input)
232+
registry.set(loader, input, promise)
233+
234+
return promise as Promise<LoadOutput<TLoader, TInput>>
235+
}
236+
}
237+
238+
function loadUrl<TInput extends LoadInput<TLoader>>(
239+
url: TInput,
240+
): Promise<LoadOutput<TLoader, TInput>> {
203241
if (config.cache === true) {
204242
if (!useLoader.cache) {
205243
return load(loader(), url)
206244
}
207245

208-
return getOrInsertLoaderRegistry(useLoader.cache, loader(), url)
246+
return getOrInsert(useLoader.cache, loader(), url)
209247
}
210248

211249
if (config.cache) {
212-
return getOrInsertLoaderRegistry(config.cache, loader(), url)
250+
return getOrInsert(config.cache, loader(), url)
213251
}
214252

215253
return load(loader(), url)
@@ -222,17 +260,6 @@ export function useLoader<
222260

223261
url = base ? resolveUrls(base, url) : url
224262

225-
if (isRecord(url)) {
226-
const result = await awaitMapObject(url, async url => {
227-
const resource = await loadUrl(url)
228-
return resource
229-
})
230-
231-
config.onLoad?.(result)
232-
233-
return result
234-
}
235-
236263
const result = await loadUrl(url)
237264

238265
config.onLoad?.(result)

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type { Measure } from "./utils/use-measure.ts"
3232
/**********************************************************************************/
3333

3434
export type AccessorMaybe<T> = T | Accessor<T>
35+
export type PromiseMaybe<T> = T | Promise<T>
3536

3637
/** Generic constructor. Returns instance of given type. Defaults to any. */
3738
export type Constructor<T = any> = new (...args: any[]) => T

0 commit comments

Comments
 (0)