import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2022-2025, University of Colorado Boulder
/**
 * PROTOTYPE version for better support
 *
 * Object pooling mixin, for cases where creating new objects is expensive, and we'd rather mark some objects as able
 * to be reused (i.e. 'in the pool'). This provides a pool of objects for each type it is invoked on. It allows for
 * getting "new" objects that can either be constructed OR pulled in from a pool, and requires that the objects are
 * essentially able to "re-run" the constructor. Then when putting the object back in the pool, references should be
 * released, so memory isn't leaked.
 *
 * With this style of pooling, the following should be standard boilerplate within the class:

 public freeToPool(): void {
 MyType.pool.freeToPool( this );
 }

 public static readonly pool = new Pool( MyType );

 * and can additionally implement TPoolable to make it clear that the type is pooled
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
import optionize from './optionize.js';
import phetCore from './phetCore.js';
export default class Pool {
    // The `initialize` option is required if the type doesn't have a correctly-typed initialize method. Therefore, we
    // do some Typescript magic to require providedOptions if that's the case (otherwise providedOptions is optional).
    constructor(type, ...providedOptionsSpread) {
        this.objects = [];
        const options = optionize()({
            defaultArguments: [],
            initialize: type.prototype.initialize,
            maxSize: 100,
            initialSize: 0,
            useDefaultConstruction: false
        }, providedOptionsSpread[0]);
        affirm(options.maxSize >= 0);
        affirm(options.initialSize >= 0);
        this._maxPoolSize = options.maxSize;
        // There is a madness to this craziness. We'd want to use the method noted at
        // https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible, but the type is
        // not provided in the arguments array below. By calling bind on itself, we're able to get a version of bind that
        // inserts the constructor as the first argument of the .apply called later so we don't create garbage by having
        // to pack `arguments` into an array AND THEN concatenate it with a new first element (the type itself).
        this.partialConstructor = Function.prototype.bind.bind(type, type);
        // Basically our type constructor, but with the default arguments included already.
        this.DefaultConstructor = this.partialConstructor(...options.defaultArguments); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
        this.initialize = options.initialize;
        affirm(this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature');
        this.useDefaultConstruction = options.useDefaultConstruction;
        // Initialize the pool (if it should have objects)
        while (this.objects.length < options.initialSize) {
            this.objects.push(this.createDefaultObject());
        }
    }
    createDefaultObject() {
        return new (this.DefaultConstructor)();
    }
    /**
     * Returns an object with arbitrary state (possibly constructed with the default arguments).
     */
    fetch() {
        return this.objects.length ? this.objects.pop() : this.createDefaultObject();
    }
    /**
     * Returns an object that behaves as if it was constructed with the given arguments. May result in a new object
     * being created (if the pool is empty), or it may use the constructor to mutate an object from the pool.
     */
    create(...args) {
        let result;
        if (this.objects.length) {
            result = this.objects.pop();
            this.initialize.apply(result, args);
        }
        else if (this.useDefaultConstruction) {
            result = this.createDefaultObject();
            this.initialize.apply(result, args);
        }
        else {
            result = new (this.partialConstructor(...args))();
        }
        return result;
    }
    /**
     * Returns the current size of the pool.
     */
    get poolSize() {
        return this.objects.length;
    }
    /**
     * Sets the maximum pool size.
     */
    set maxPoolSize(value) {
        affirm(value === Number.POSITIVE_INFINITY || (Number.isInteger(value) && value >= 0), 'maxPoolSize should be a non-negative integer or infinity');
        this._maxPoolSize = value;
    }
    /**
     * Returns the maximum pool size.
     */
    get maxPoolSize() {
        return this._maxPoolSize;
    }
    freeToPool(object) {
        if (this.objects.length < this.maxPoolSize) {
            this.objects.push(object);
        }
    }
    forEach(callback) {
        this.objects.forEach(callback);
    }
}
phetCore.register('Pool', Pool);
//# sourceMappingURL=Pool.js.map