kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
466 lines (463 loc) • 16.2 kB
JavaScript
import { Assets, Rectangle, Texture } from 'pixi.js';
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
const isDevelopment = false;
class AtlasManager {
/**
* Create a new atlas manager
*
* @param options - Options for the atlas manager
* @param resourceManager - Optional ResourceManager for tracking resources
*/
constructor(options = {}, resourceManager) {
// Loaded atlases
__publicField(this, "atlases", /* @__PURE__ */ new Map());
// Atlas textures (the full atlas texture)
__publicField(this, "atlasTextures", /* @__PURE__ */ new Map());
// Cached frame textures (individual sprites cut from the atlas)
__publicField(this, "frameTextures", /* @__PURE__ */ new Map());
// Image textures (for individual images, used as fallback)
__publicField(this, "imageTextures", /* @__PURE__ */ new Map());
// Loading status for atlases
__publicField(this, "atlasStatus", /* @__PURE__ */ new Map());
// Loading status for individual images
__publicField(this, "imageStatus", /* @__PURE__ */ new Map());
// Reference to ResourceManager for tracking resources
__publicField(this, "resourceManager");
// Options for the atlas manager
__publicField(this, "options");
this.options = {
debug: isDevelopment,
preferAtlas: true,
cacheFrameTextures: true,
basePath: "",
...options
};
this.resourceManager = resourceManager;
if (this.options.debug) {
console.log("AtlasManager initialized with options:", this.options);
}
}
/**
* Extract the filename from a path for atlas frame lookup
*
* @param imagePath - The full path to the image
* @returns The filename part of the path
*/
getFilenameFromPath(imagePath) {
if (!imagePath) return "";
const filename = imagePath.split("/").pop() || imagePath;
if (this.options.debug) {
console.log(`Extracted filename "${filename}" from path "${imagePath}"`);
}
return filename;
}
/**
* Load a texture atlas from a JSON file
*
* @param atlasId - Identifier for the atlas
* @param jsonUrl - URL to the atlas JSON file
* @param imageUrl - Optional URL to the atlas image file (if not specified in JSON)
* @returns Promise resolving when the atlas is loaded
*/
async loadAtlas(atlasId, jsonUrl, imageUrl) {
if (this.atlasStatus.get(atlasId) === "loaded" /* Loaded */) {
this.log(`Atlas '${atlasId}' already loaded, skipping`);
return true;
}
if (this.atlasStatus.get(atlasId) === "loading" /* Loading */) {
this.log(`Atlas '${atlasId}' is already loading, waiting...`);
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
const status = this.atlasStatus.get(atlasId);
if (status === "loaded" /* Loaded */) {
clearInterval(checkInterval);
resolve(true);
} else if (status === "failed" /* Failed */) {
clearInterval(checkInterval);
resolve(false);
}
}, 100);
});
}
try {
this.atlasStatus.set(atlasId, "loading" /* Loading */);
this.log(`Loading atlas '${atlasId}' from ${jsonUrl}`);
let atlasData;
try {
const response = await fetch(jsonUrl);
if (!response.ok) {
throw new Error(`Failed to load atlas JSON: ${response.statusText}`);
}
atlasData = await response.json();
} catch (error) {
this.log(`Error loading atlas JSON: ${error}`, "error");
this.atlasStatus.set(atlasId, "failed" /* Failed */);
return false;
}
if (!atlasData.frames || !atlasData.meta) {
this.log(`Invalid atlas data: missing frames or meta`, "error");
this.atlasStatus.set(atlasId, "failed" /* Failed */);
return false;
}
const atlasImageUrl = imageUrl || (atlasData.meta.image ? this.options.basePath ? `${this.options.basePath}/${atlasData.meta.image}` : atlasData.meta.image : null);
if (!atlasImageUrl) {
this.log(`Invalid atlas data: no image URL specified`, "error");
this.atlasStatus.set(atlasId, "failed" /* Failed */);
return false;
}
let atlasTexture;
try {
if (Assets.cache.has(atlasImageUrl)) {
atlasTexture = Assets.cache.get(atlasImageUrl);
} else {
this.log(`Loading atlas texture from ${atlasImageUrl}`);
atlasTexture = await Assets.load(atlasImageUrl);
}
if (this.resourceManager) {
this.resourceManager.trackTexture(atlasImageUrl, atlasTexture);
}
} catch (error) {
this.log(`Error loading atlas texture: ${error}`, "error");
this.atlasStatus.set(atlasId, "failed" /* Failed */);
return false;
}
this.atlases.set(atlasId, atlasData);
this.atlasTextures.set(atlasId, atlasTexture);
this.atlasStatus.set(atlasId, "loaded" /* Loaded */);
this.log(`Atlas '${atlasId}' loaded successfully with ${Object.keys(atlasData.frames).length} frames`);
return true;
} catch (error) {
this.log(`Unexpected error loading atlas: ${error}`, "error");
this.atlasStatus.set(atlasId, "failed" /* Failed */);
return false;
}
}
/**
* Check if a frame exists in any loaded atlas
*
* @param frameName - Name of the frame to check
* @returns The ID of the atlas containing the frame, or null if not found
*/
hasFrame(frameName) {
for (const [atlasId, atlas] of this.atlases.entries()) {
if (atlas.frames[frameName]) {
return atlasId;
}
}
if (frameName.includes("/")) {
const filename = this.getFilenameFromPath(frameName);
for (const [atlasId, atlas] of this.atlases.entries()) {
if (atlas.frames[filename]) {
if (this.options.debug) {
console.log(`Found frame "${filename}" in atlas "${atlasId}" by extracting from path "${frameName}"`);
}
return atlasId;
}
}
}
return null;
}
/**
* Get a texture for a frame from an atlas
*
* @param frameName - Name of the frame
* @param atlasId - Optional ID of the atlas to use (if not specified, all atlases will be searched)
* @returns The texture for the frame, or null if not found
*/
getFrameTexture(frameName, atlasId) {
const cacheKey = atlasId ? `${atlasId}:${frameName}` : frameName;
if (this.options.cacheFrameTextures && this.frameTextures.has(cacheKey)) {
return this.frameTextures.get(cacheKey);
}
let targetAtlasId = atlasId;
let frameData = null;
let lookupName = frameName;
if (frameName.includes("/")) {
lookupName = this.getFilenameFromPath(frameName);
}
if (targetAtlasId) {
const atlas = this.atlases.get(targetAtlasId);
if (!atlas) {
this.log(`Atlas '${targetAtlasId}' not found`, "warn");
return null;
}
frameData = atlas.frames[frameName] || null;
if (!frameData && frameName !== lookupName) {
frameData = atlas.frames[lookupName] || null;
if (frameData && this.options.debug) {
console.log(`Found frame "${lookupName}" in atlas "${targetAtlasId}" by extracting from path "${frameName}"`);
}
}
if (!frameData) {
this.log(`Frame '${frameName}' not found in atlas '${targetAtlasId}'`, "warn");
return null;
}
} else {
for (const [id, atlas] of this.atlases.entries()) {
if (atlas.frames[frameName]) {
targetAtlasId = id;
frameData = atlas.frames[frameName];
break;
}
if (frameName !== lookupName && atlas.frames[lookupName]) {
targetAtlasId = id;
frameData = atlas.frames[lookupName];
if (this.options.debug) {
console.log(`Found frame "${lookupName}" in atlas "${id}" by extracting from path "${frameName}"`);
}
break;
}
}
if (!targetAtlasId || !frameData) {
this.log(`Frame '${frameName}' not found in any atlas`, "warn");
return null;
}
}
const atlasTexture = this.atlasTextures.get(targetAtlasId);
if (!atlasTexture) {
this.log(`Atlas texture for '${targetAtlasId}' not found`, "warn");
return null;
}
try {
const rect = new Rectangle(
frameData.frame.x,
frameData.frame.y,
frameData.frame.w,
frameData.frame.h
);
const source = atlasTexture.source;
const frameTexture = new Texture({
source,
// Use the same source as the atlas texture
frame: rect,
// The frame rectangle within the atlas
orig: frameData.trimmed && frameData.spriteSourceSize ? new Rectangle(
frameData.spriteSourceSize.x,
frameData.spriteSourceSize.y,
frameData.spriteSourceSize.w,
frameData.spriteSourceSize.h
) : void 0,
trim: frameData.trimmed ? rect : void 0,
rotate: frameData.rotated ? 2 : 0
// 2 = DEGREES_90, 0 = DEGREES_0
});
if (this.options.cacheFrameTextures) {
this.frameTextures.set(cacheKey, frameTexture);
if (this.resourceManager) {
}
}
return frameTexture;
} catch (error) {
this.log(`Error creating frame texture: ${error}`, "error");
return null;
}
}
/**
* Get a list of all frame names in an atlas
*
* @param atlasId - ID of the atlas
* @returns Array of frame names, or empty array if atlas not found
*/
getFrameNames(atlasId) {
const atlas = this.atlases.get(atlasId);
if (!atlas) {
return [];
}
return Object.keys(atlas.frames);
}
/**
* Get a texture for an image, either from an atlas or as an individual texture
*
* @param imagePath - Path to the image
* @param atlasId - Optional ID of a specific atlas to check first
* @returns Promise resolving to the texture, or null if not found
*/
async getTexture(imagePath, atlasId) {
if (this.options.preferAtlas) {
let frameTexture = this.getFrameTexture(imagePath, atlasId);
if (!frameTexture && imagePath.includes("/")) {
const filename = this.getFilenameFromPath(imagePath);
frameTexture = this.getFrameTexture(filename, atlasId);
if (frameTexture && this.options.debug) {
console.log(`Using atlas frame for ${imagePath} (found using filename ${filename}) ${atlasId ? `from atlas '${atlasId}'` : ""}`);
}
return frameTexture;
}
if (frameTexture) {
this.log(`Using atlas frame for ${imagePath} ${atlasId ? `from atlas '${atlasId}'` : ""}`);
return frameTexture;
}
}
if (this.imageTextures.has(imagePath)) {
return this.imageTextures.get(imagePath);
}
const status = this.imageStatus.get(imagePath);
if (status === "loading" /* Loading */) {
this.log(`Image ${imagePath} is already loading, waiting...`);
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
const currentStatus = this.imageStatus.get(imagePath);
if (currentStatus === "loaded" /* Loaded */) {
clearInterval(checkInterval);
resolve(this.imageTextures.get(imagePath) || null);
} else if (currentStatus === "failed" /* Failed */) {
clearInterval(checkInterval);
resolve(null);
}
}, 100);
});
}
try {
this.imageStatus.set(imagePath, "loading" /* Loading */);
let texture;
if (Assets.cache.has(imagePath)) {
texture = Assets.cache.get(imagePath);
} else {
this.log(`Loading individual texture from ${imagePath}`);
texture = await Assets.load(imagePath);
}
this.imageTextures.set(imagePath, texture);
this.imageStatus.set(imagePath, "loaded" /* Loaded */);
if (this.resourceManager) {
this.resourceManager.trackTexture(imagePath, texture);
}
return texture;
} catch (error) {
this.log(`Error loading individual texture ${imagePath}: ${error}`, "error");
this.imageStatus.set(imagePath, "failed" /* Failed */);
return null;
}
}
/**
* Preload a set of images, preferably from atlas(es)
*
* @param imagePaths - Array of image paths to preload
* @param atlasIds - Optional array of atlas IDs to search for frames
* @param progressCallback - Optional callback for loading progress
* @returns Promise resolving when all images are loaded
*/
async preloadImages(imagePaths, atlasIds, progressCallback) {
if (!imagePaths.length) {
return;
}
let loadedCount = 0;
const totalCount = imagePaths.length;
const updateProgress = () => {
loadedCount++;
if (progressCallback) {
progressCallback(loadedCount / totalCount);
}
};
const loadPromises = imagePaths.map(async (imagePath) => {
try {
if (atlasIds && atlasIds.length > 0) {
for (const atlasId of atlasIds) {
const texture = await this.getTexture(imagePath, atlasId);
if (texture) {
updateProgress();
return;
}
}
await this.getTexture(imagePath);
} else {
await this.getTexture(imagePath);
}
updateProgress();
} catch (error) {
this.log(`Error preloading image ${imagePath}: ${error}`, "warn");
updateProgress();
}
});
await Promise.all(loadPromises);
}
/**
* Unload an atlas and its resources
*
* @param atlasId - ID of the atlas to unload
*/
unloadAtlas(atlasId) {
if (!this.atlases.has(atlasId)) {
this.log(`Atlas '${atlasId}' not found, cannot unload`, "warn");
return;
}
try {
const atlasTexture = this.atlasTextures.get(atlasId);
if (this.options.cacheFrameTextures) {
const prefix = `${atlasId}:`;
for (const [key] of this.frameTextures.entries()) {
if (key.startsWith(prefix)) {
this.frameTextures.delete(key);
}
}
}
this.atlases.delete(atlasId);
if (atlasTexture) {
this.atlasTextures.delete(atlasId);
}
this.atlasStatus.delete(atlasId);
this.log(`Atlas '${atlasId}' unloaded`);
} catch (error) {
this.log(`Error unloading atlas '${atlasId}': ${error}`, "error");
}
}
/**
* Unload an individual image texture
*
* @param imagePath - Path to the image
*/
unloadTexture(imagePath) {
if (!this.imageTextures.has(imagePath)) {
return;
}
try {
this.imageTextures.delete(imagePath);
this.imageStatus.delete(imagePath);
this.log(`Texture ${imagePath} unloaded`);
} catch (error) {
this.log(`Error unloading texture ${imagePath}: ${error}`, "error");
}
}
/**
* Clean up all resources
*/
dispose() {
try {
this.atlases.clear();
this.atlasTextures.clear();
this.frameTextures.clear();
this.imageTextures.clear();
this.atlasStatus.clear();
this.imageStatus.clear();
this.log("AtlasManager disposed");
} catch (error) {
this.log(`Error disposing AtlasManager: ${error}`, "error");
}
}
/**
* Log a message with the appropriate level
*
* @param message - Message to log
* @param level - Log level
*/
log(message, level = "log") {
if (!this.options.debug && level === "log") {
return;
}
const prefix = "[AtlasManager]";
switch (level) {
case "warn":
console.warn(`${prefix} ${message}`);
break;
case "error":
console.error(`${prefix} ${message}`);
break;
default:
console.log(`${prefix} ${message}`);
break;
}
}
}
export { AtlasManager };
//# sourceMappingURL=AtlasManager.js.map