_import { isArray } from './data-size.js';

/**
 * Uniform is a container for uniform buffer related data and actions.
 *
 * @class Uniform
 */
class Uniform {
    #name
    #value
    #type
    #size

    /**
     *
     * @param {{name:String, value:(Number|Boolean|Array<Number>), type:string, size:Number=}} config
     */
    constructor({ name, value, type = null, size = null }) {

        this.#validateName(name);
        this.#validateType(type);
        this.#validateValue(value);

        this.#name = name;

        this.#value = value;
        this.#type = type || this.#getArrayType(value) || 'f32';
        this.#size = size;

        Object.seal(this);
    }

    get name() {
        return this.#name;
    }

    /**
     * The name that the Uniform will have on the WGSL side.
     * @param {String} value name of the Uniform. The name is used in the WGSL
     * shader.
     * @example
     * // js
     * myUniform.name = 'myUniformName';
     *
     * // wgsl
     * myUniformName = 13.0;
     * @memberof Uniform
     */
    set name(value) {
        this.#validateName(value);
        this.#name = value;
    }

    get value() {
        return this.#value;
    }

    /**
     * To get or set the value of the uniform from the JS side to the WGSL side.
     * @param {Number|Boolean|Array<Number>} value The uniform value
     * @memberof Uniform
     */
    set value(value) {
        this.#validateValue(value);
        this.#value = value;
    }

    get type() {
        return this.#type
    }

    /**
     * Get or set the type of the uniform.
     * It can be inferred automatically by just passing the value, but if
     * something more specific is required, then you should use `type`.
     * @param {String} value WGSL data type of the uniform
     * @example
     * myUniform.type = 'u32';
     * @memberof Uniform
     */
    set type(value) {
        this.#validateType(value);
        this.#type = value || this.#getArrayType(this.#value) || 'f32';
    }

    get size() {
        return this.#size;
    }

    /**
     * For internal use mostly. Size in bytes.
     * @memberof Uniform
     */
    set size(value) {
        this.#size = value;
    }

    /**
     * Clone of the Uniform data as a plain object to avoid modifications on
     * the original data.
     * @returns {Object}
     * @memberof Uniform
     */
    serialize() {
        // we check if array and spread
        // because structuredClone is slower
        const isArray = Array.isArray(this.#value);
        const value = isArray ? [...this.#value] : this.#value;
        return {
            name: this.#name,
            value,
            type: this.#type,
            size: this.#size
        };
    }

    /**
     * Sets or updates the value of the Uniform.
     * @param {Number|Boolean|Array<Number>} value
     * @memberof Uniform
     */
    setValue(value) {
        this.#validateValue(value);
        this.#value = value;
        return this;
    }

    /**
     * Set the data type of the uniform.
     * @param {String} value WGSL data type of the uniform
     * @example
     * myUniform.setType('u32')
     * @memberof Uniform
     */
    setType(value) {
        this.#validateType(value);
        this.#type = value || this.#getArrayType(this.#value) || 'f32';
        return this;
    }

    #validateValue(value) {
        if (typeof value === 'object' && !Array.isArray(value)) {
            throw `Uniform '${this.#name}' value:'${value}' can't be an Object.`
        }

        if (typeof value === 'string') {
            throw `Uniform '${this.#name}' value: '${value}' can't be an String.`
        }

        const isArray = Array.isArray(value);
        if (isArray) {
            const { length } = value;
            // TODO include mat values, e.g.: mat4x2
            // if (length > 4) {
            //     console.trace(this.#name, this.#value);
            //     throw `Uniform named '${this.#name}': Can't assign an Array greater than a vec4f.`
            // }
            if (Array.isArray(this.#value)) {
                if (length != this.#value.length) {
                    throw `Uniform named '${this.#name}': Size of the array value has changed from ${this.#value.length} to ${length}.`
                }
            }

            if (length < 2) {
                throw `Uniform named '${this.#name}': Can't assign an Array smaller than a vec2f. Assign the Number directly.`
            }
        }
    }

    #validateName(value) {
        if (typeof value === 'number') {
            throw `Uniform name '${this.#name}' can't be an Number.`
        }

        if (typeof value === 'string') {
            const valNumber = +value;

            if (!Number.isNaN(valNumber) && typeof valNumber === 'number') {
                throw `Uniform name '${this.#name}' can't be an Number.`
            }
        }
    }

    #validateType(value) {
        if (value && isArray(value)) {
            throw `Uniform '${this.#name}' type: '${value}' is an array, which is currently not supported for Uniforms.`;
        }
    }

    /**
     * There's already a `getArrayType` in data-size.js
     * but since uniforms can't accept array in wgsl,
     * this method excludes that part
     * returns something like vec2f, vec3f
     * @param {Array|Object} value
     * @returns {String}
     */
    #getArrayType(value) {
        const isArray = Array.isArray(value);
        let type = null;
        if (isArray) {
            const { length } = value
            if (length <= 4) {
                type = `vec${length}f`;
            }
        }
        return type;
    }

    // allows for things like:
    // uniforms.myUniform += 10
    // works on set, not on get
    // on get you obtain the Uniform
    valueOf() {
        return this.#value;
    }
}

export default Uniform;

MIT

Documentation generated by JSDoc 4.0.5 using Docolatte theme on