pixi.js
Version:
<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">
278 lines (275 loc) • 8.83 kB
JavaScript
import { ExtensionType } from '../../../extensions/Extensions.mjs';
"use strict";
const _GCSystem = class _GCSystem {
/**
* Creates a new GCSystem instance.
* @param renderer - The renderer this garbage collection system works for
*/
constructor(renderer) {
/** Array of resources being tracked for garbage collection */
this._managedResources = [];
this._managedResourceHashes = [];
this._ready = false;
this._renderer = renderer;
}
/**
* Initializes the garbage collection system with the provided options.
* @param options - Configuration options
*/
init(options) {
options = { ..._GCSystem.defaultOptions, ...options };
this.maxUnusedTime = options.gcMaxUnusedTime;
this._frequency = options.gcFrequency;
this.enabled = options.gcActive;
this.now = performance.now();
}
/**
* Gets whether the garbage collection system is currently enabled.
* @returns True if GC is enabled, false otherwise
*/
get enabled() {
return !!this._handler;
}
/**
* Enables or disables the garbage collection system.
* When enabled, schedules periodic cleanup of resources.
* When disabled, cancels all scheduled cleanups.
*/
set enabled(value) {
if (this.enabled === value)
return;
if (value) {
this._handler = this._renderer.scheduler.repeat(
() => {
this._ready = true;
},
this._frequency,
false
);
} else {
this._renderer.scheduler.cancel(this._handler);
this._handler = 0;
}
}
/**
* Called before rendering. Updates the current timestamp.
* @param options - The render options
* @param options.container - The container to render
*/
prerender({ container }) {
this.now = performance.now();
container.renderGroup.gcTick = this._renderer.tick++;
this._updateInstructionGCTick(container.renderGroup, container.renderGroup.gcTick);
}
/** Performs garbage collection after rendering. */
postrender() {
if (!this._ready || !this.enabled)
return;
this.run();
this._ready = false;
}
/**
* Updates the GC tick counter for a render group and its children.
* @param renderGroup - The render group to update
* @param gcTick - The new tick value
*/
_updateInstructionGCTick(renderGroup, gcTick) {
renderGroup.instructionSet.gcTick = gcTick;
for (const child of renderGroup.renderGroupChildren) {
this._updateInstructionGCTick(child, gcTick);
}
}
/**
* Registers a resource for garbage collection tracking.
* @param resource - The resource to track
* @param type - The type of resource to track
*/
addResource(resource, type) {
if (resource._gcLastUsed !== -1) {
resource._gcLastUsed = this.now;
resource._onTouch?.(this.now);
return;
}
const index = this._managedResources.length;
resource._gcData = {
index,
type
};
resource._gcLastUsed = this.now;
resource._onTouch?.(this.now);
resource.once("unload", this.removeResource, this);
this._managedResources.push(resource);
}
/**
* Removes a resource from garbage collection tracking.
* Call this when manually destroying a resource.
* @param resource - The resource to stop tracking
*/
removeResource(resource) {
const gcData = resource._gcData;
if (!gcData)
return;
const index = gcData.index;
const last = this._managedResources.length - 1;
if (index !== last) {
const lastResource = this._managedResources[last];
this._managedResources[index] = lastResource;
lastResource._gcData.index = index;
}
this._managedResources.length--;
resource._gcData = null;
resource._gcLastUsed = -1;
}
/**
* Registers a hash-based resource collection for garbage collection tracking.
* Resources in the hash will be automatically tracked and cleaned up when unused.
* @param context - The object containing the hash property
* @param hash - The property name on context that holds the resource hash
* @param type - The type of resources in the hash ('resource' or 'renderable')
* @param priority - Processing priority (lower values are processed first)
*/
addResourceHash(context, hash, type, priority = 0) {
this._managedResourceHashes.push({
context,
hash,
type,
priority
});
this._managedResourceHashes.sort((a, b) => a.priority - b.priority);
}
/**
* Performs garbage collection by cleaning up unused resources.
* Removes resources that haven't been used for longer than maxUnusedTime.
*/
run() {
const now = performance.now();
const managedResourceHashes = this._managedResourceHashes;
for (const hashEntry of managedResourceHashes) {
this.runOnHash(hashEntry, now);
}
let writeIndex = 0;
for (let i = 0; i < this._managedResources.length; i++) {
const resource = this._managedResources[i];
writeIndex = this.runOnResource(resource, now, writeIndex);
}
this._managedResources.length = writeIndex;
}
updateRenderableGCTick(renderable, now) {
const renderGroup = renderable.renderGroup ?? renderable.parentRenderGroup;
const currentTick = renderGroup?.instructionSet?.gcTick ?? -1;
if ((renderGroup?.gcTick ?? 0) === currentTick) {
renderable._gcLastUsed = now;
renderable._onTouch?.(now);
}
}
runOnResource(resource, now, writeIndex) {
const gcData = resource._gcData;
if (gcData.type === "renderable") {
this.updateRenderableGCTick(resource, now);
}
const isRecentlyUsed = now - resource._gcLastUsed < this.maxUnusedTime;
if (isRecentlyUsed || !resource.autoGarbageCollect) {
this._managedResources[writeIndex] = resource;
gcData.index = writeIndex;
writeIndex++;
} else {
resource.unload();
resource._gcData = null;
resource._gcLastUsed = -1;
resource.off("unload", this.removeResource, this);
}
return writeIndex;
}
/**
* Creates a clone of the hash, copying all non-null entries up to (but not including) the stop key.
* @param hashValue - The original hash to clone from
* @param stopKey - The key to stop at (exclusive)
* @returns A new hash object with copied entries
*/
_createHashClone(hashValue, stopKey) {
const hashClone = /* @__PURE__ */ Object.create(null);
for (const k in hashValue) {
if (k === stopKey)
break;
if (hashValue[k] !== null)
hashClone[k] = hashValue[k];
}
return hashClone;
}
runOnHash(hashEntry, now) {
const { context, hash, type } = hashEntry;
const hashValue = context[hash];
let hashClone = null;
let nullCount = 0;
for (const key in hashValue) {
const resource = hashValue[key];
if (resource === null) {
nullCount++;
if (nullCount === 1e4 && !hashClone) {
hashClone = this._createHashClone(hashValue, key);
}
continue;
}
if (resource._gcLastUsed === -1) {
resource._gcLastUsed = now;
resource._onTouch?.(now);
if (hashClone)
hashClone[key] = resource;
continue;
}
if (type === "renderable") {
this.updateRenderableGCTick(resource, now);
}
const isRecentlyUsed = now - resource._gcLastUsed < this.maxUnusedTime;
if (!isRecentlyUsed && resource.autoGarbageCollect) {
if (!hashClone) {
if (nullCount + 1 !== 1e4) {
hashValue[key] = null;
nullCount++;
} else {
hashClone = this._createHashClone(hashValue, key);
}
}
resource.unload();
resource._gcData = null;
resource._gcLastUsed = -1;
} else if (hashClone) {
hashClone[key] = resource;
}
}
if (hashClone) {
context[hash] = hashClone;
}
}
/** Cleans up the garbage collection system. Disables GC and removes all tracked resources. */
destroy() {
this.enabled = false;
this._managedResources.forEach((resource) => {
resource.off("unload", this.removeResource, this);
});
this._managedResources.length = 0;
this._managedResourceHashes.length = 0;
this._renderer = null;
}
};
/** @ignore */
_GCSystem.extension = {
type: [
ExtensionType.WebGLSystem,
ExtensionType.WebGPUSystem
],
name: "gc",
priority: 0
};
/** Default options for the GCSystem */
_GCSystem.defaultOptions = {
/** Enable/disable the garbage collector */
gcActive: true,
/** Time in ms before an unused resource is collected (default 1 minute) */
gcMaxUnusedTime: 6e4,
/** How often to run garbage collection in ms (default 30 seconds) */
gcFrequency: 3e4
};
let GCSystem = _GCSystem;
export { GCSystem };
//# sourceMappingURL=GCSystem.mjs.map