@deck.gl/core
Version:
deck.gl core library
182 lines (159 loc) • 4.65 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
/* global setTimeout */
import {Device} from '@luma.gl/core';
import Resource from './resource';
import type {ResourceSubscriber} from './resource';
export type ResourceManagerContext = {
device: Device;
resourceManager: ResourceManager;
/** @deprecated */
gl: WebGL2RenderingContext;
};
type Consumer = Record<string, ResourceSubscriber & {resourceId: string}>;
export default class ResourceManager {
protocol: string;
private _context: ResourceManagerContext;
private _resources: Record<string, Resource>;
private _consumers: Record<string, Consumer>;
private _pruneRequest: number | null;
constructor(props: {device: Device; protocol?: string}) {
this.protocol = props.protocol || 'resource://';
this._context = {
device: props.device,
// @ts-expect-error
gl: props.device?.gl,
resourceManager: this
};
this._resources = {};
this._consumers = {};
this._pruneRequest = null;
}
contains(resourceId: string): boolean {
if (resourceId.startsWith(this.protocol)) {
return true;
}
return resourceId in this._resources;
}
add({
resourceId,
data,
forceUpdate = false,
persistent = true
}: {
resourceId: string;
data: any;
forceUpdate?: boolean;
persistent?: boolean;
}) {
let res = this._resources[resourceId];
if (res) {
res.setData(data, forceUpdate);
} else {
res = new Resource(resourceId, data, this._context);
this._resources[resourceId] = res;
}
// persistent resources can only be removed by calling `remove`
// non-persistent resources may be released when there are no more consumers
res.persistent = persistent;
}
remove(resourceId: string): void {
const res = this._resources[resourceId];
if (res) {
res.delete();
delete this._resources[resourceId];
}
}
unsubscribe({consumerId}: {consumerId: string}): void {
const consumer = this._consumers[consumerId];
if (consumer) {
for (const requestId in consumer) {
const request = consumer[requestId];
const resource = this._resources[request.resourceId];
if (resource) {
resource.unsubscribe(request);
}
}
delete this._consumers[consumerId];
this.prune();
}
}
subscribe<T>({
resourceId,
onChange,
consumerId,
requestId = 'default'
}: {
resourceId: string;
onChange: (data: T | Promise<T>) => void;
consumerId: string;
requestId: string;
}): T | Promise<T> | undefined {
const {_resources: resources, protocol} = this;
if (resourceId.startsWith(protocol)) {
resourceId = resourceId.replace(protocol, '');
if (!resources[resourceId]) {
// Add placeholder. When this resource becomes available, the consumer will be notified.
this.add({resourceId, data: null, persistent: false});
}
}
const res: Resource<T> = resources[resourceId];
this._track(consumerId, requestId, res, onChange);
if (res) {
return res.getData();
}
return undefined;
}
prune(): void {
if (!this._pruneRequest) {
// prune() may be called multiple times in the same animation frame.
// Batch multiple requests together
// @ts-ignore setTimeout returns NodeJS.Timeout in node
this._pruneRequest = setTimeout(() => this._prune(), 0);
}
}
finalize(): void {
for (const key in this._resources) {
this._resources[key].delete();
}
}
private _track(
consumerId: string,
requestId: string,
resource: Resource,
onChange: (data: any) => void
) {
const consumers = this._consumers;
const consumer = (consumers[consumerId] = consumers[consumerId] || {});
let request = consumer[requestId];
const oldResource = request && request.resourceId && this._resources[request.resourceId];
if (oldResource) {
oldResource.unsubscribe(request);
this.prune();
}
if (resource) {
if (request) {
request.onChange = onChange;
request.resourceId = resource.id;
} else {
request = {
onChange,
resourceId: resource.id
};
}
consumer[requestId] = request;
resource.subscribe(request);
}
}
private _prune(): void {
this._pruneRequest = null;
for (const key of Object.keys(this._resources)) {
const res = this._resources[key];
if (!res.persistent && !res.inUse()) {
res.delete();
delete this._resources[key];
}
}
}
}