'use strict';

var Extensions = require('../../../extensions/Extensions.js');

"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: [
    Extensions.ExtensionType.WebGLSystem,
    Extensions.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;

exports.GCSystem = GCSystem;
//# sourceMappingURL=GCSystem.js.map
