Source: model/Utils/Resources/Resources.js

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import EventEmitter from '../../../controller/Utils/EventEmitter';
import { ResourceTypes } from './ResourcesTypes';
import { Events } from '../../../controller/Utils/Events';

/**
 * Resources class for managing and loading various types of assets asynchronously.
 * Extends EventEmitter to handle event-driven architecture.
 *
 * This class handles the loading of different types of resources such as models,
 * textures, cube textures, and audio files. It tracks the loading progress and emits
 * events when resources are ready for use.
 *
 * @class
 * @extends EventEmitter
 */
class Resources extends EventEmitter {
    /**
     * Creates an instance of Resources.
     * Initializes loaders and starts loading resources.
     *
     * @param {Source[]} sources - Array of Source instances describing each resource to load.
     * @constructor
     */
    constructor(sources) {
        super();
        this.sources = sources;
        this.items = {};
        this.toLoad = this.sources.length;
        this.loaded = 0;
        this.setLoaders();
        this.startLoading();
    }

    /**
     * Initializes various loaders needed for different resource types.
     *
     * @private
     */
    setLoaders() {
        this.loaders = {
            gltfLoader: new GLTFLoader(),
            textureLoader: new THREE.TextureLoader(),
            cubeTextureLoader: new THREE.CubeTextureLoader(),
            audioLoader: new THREE.AudioLoader(),
            dracoLoader: new DRACOLoader(),
        };
    }

    /**
     * Starts loading each resource based on its type.
     * Triggers corresponding loaders and handles successful loading with sourceLoaded().
     *
     * @private
     */
    startLoading() {
        for (const source of this.sources) {
            switch (source.type) {
                case ResourceTypes.GLTFModel:
                    this.loaders.gltfLoader.load(source.path, (file) => {
                        this.sourceLoaded(source, file);
                    }, (xhr) => { }, // onProgress callback
                        (error) => console.error(`Error loading GLTFModel: ${source.name}`, error));
                    break;
                case ResourceTypes.Texture:
                    this.loaders.textureLoader.load(source.path, (file) => {
                        this.sourceLoaded(source, file);
                    }, (xhr) => { }, // onProgress callback
                        (error) => console.error(`Error loading Texture: ${source.name}`, error));
                    break;
                case ResourceTypes.CubeTexture:
                    this.loaders.cubeTextureLoader.load(source.path, (file) => {
                        this.sourceLoaded(source, file);
                    },
                        (xhr) => { }, // onProgress callback
                        (error) => console.error(`Error loading CubeTexture: ${source.name}`, error));
                    break;
                case ResourceTypes.Audio:
                    this.loaders.audioLoader.load(source.path, (file) => {
                        this.sourceLoaded(source, file);
                    },
                        (xhr) => { }, // onProgress callback
                        (error) => console.error(`Error loading audio: ${source.name}`, error));
                    break;
                default:
                    console.warn(`Unknown resource type: ${source.type}`);
                    break;
            }
        }
    }

    /**
     * Handles successful loading of a resource.
     * Stores the loaded file in this.items with the source name and triggers 'ready' event
     * when all resources are loaded.
     *
     * @param {Source} source - Description of the loaded resource.
     * @param {any} file - Loaded file object (depends on the loader used).
     * @private
     */
    sourceLoaded(source, file) {
        this.items[source.name] = file;
        this.loaded++;
        this.emitProgress();
        if (this.loaded === this.toLoad) {
            this.trigger(Events.ResourcesReady);
        }
    }

    /**
     * Emits progress updates for the loading process.
     * This method calculates the loading progress and triggers the corresponding
     * event to notify listeners about the current loading status.
     *
     * @private
     */
    emitProgress() {
        this.progress = this.loaded / this.toLoad;
        this.trigger(Events.ResourceProgress);
    }

    /**
     * Retrieves a loaded resource by its name.
     * The name can be a string or a value from the ResourceNames enum.
     *
     * @param {ResourceNames | string} name - The name of the resource to retrieve.
     * @returns {any} - The loaded resource, or undefined if not found.
     */
    getResource(name) {
        return this.items[name];
    }
}

export default Resources;