/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package org.illarion.engine.backend.shared; import illarion.common.util.ProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; /** * The purpose of this task is to read the XML file of one set of texture atlas files. * * @author Martin Karing <nitram@illarion.org> */ public class TextureAtlasListXmlLoadingTask<T> implements Runnable, TextureAtlasTask { /** * The logger that provides the logging output of this class. */ private static final Logger log = LoggerFactory.getLogger(TextureAtlasListXmlLoadingTask.class); /** * The factory required to create the XML parser. Setting up this parser should be done before its assigned to * this task. */ @Nonnull private final XmlPullParserFactory parserFactory; /** * The name of the atlas. Its required to fetch the correct XML file. */ @Nonnull private final String atlasName; /** * The parent texture manager. This one is needed to fetch the actual texture files later during the loading * progress. */ @Nonnull private final AbstractTextureManager<T> textureManager; /** * The progress monitor that is supposed to keep track of this loading task. */ @Nonnull private final ProgressMonitor progressMonitor; /** * The executor that takes care for the execution of the tasks. */ @Nullable private final Executor taskExecutor; /** * Stores if the task is done. */ private boolean done; /** * Create a new loading task. This task is meant to be executed concurrently. It will request the required * textures from the parent texture manager once the loading is progressed to this point. * * @param parserFactory the parser factory * @param atlasName the name of the atlas files * @param textureManager the parent texture manager * @param progressMonitor the monitor of the loading progress * @param taskExecutor the executor that takes care for executing further tasks. */ public TextureAtlasListXmlLoadingTask( @Nonnull XmlPullParserFactory parserFactory, @Nonnull String atlasName, @Nonnull AbstractTextureManager<T> textureManager, @Nonnull ProgressMonitor progressMonitor, @Nullable Executor taskExecutor) { this.parserFactory = parserFactory; this.atlasName = atlasName; this.textureManager = textureManager; this.progressMonitor = progressMonitor; this.taskExecutor = taskExecutor; done = false; progressMonitor.setProgress(0.f); } @Override public boolean isDone() { return done; } @Override public void run() { InputStream xmlStream = null; try { XmlPullParser parser = parserFactory.newPullParser(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); xmlStream = classLoader.getResourceAsStream(atlasName + "-atlas.xml"); parser.setInput(xmlStream, "UTF-8"); int currentEvent = parser.nextTag(); int expectedAtlasCount = 0; @Nullable TextureAtlasFinalizeTask<T> currentTextureTask = null; while (currentEvent != XmlPullParser.END_DOCUMENT) { if (currentEvent == XmlPullParser.START_TAG) { String tagName = parser.getName(); switch (tagName) { case "atlasList": expectedAtlasCount = getExpectedAtlasCount(parser, expectedAtlasCount); if (expectedAtlasCount > 0) { progressMonitor.setWeight(expectedAtlasCount); } break; case "atlas": @Nullable String currentAtlasName = getAtlasTextureName(parser); if (currentAtlasName != null) { FutureTask<T> preLoadTask = new FutureTask<>( new TextureAtlasPreLoadTask<>(textureManager, currentAtlasName)); if (taskExecutor == null) { preLoadTask.run(); } else { taskExecutor.execute(preLoadTask); } float progressToAdd = (expectedAtlasCount == 0) ? 0.f : (1.f / expectedAtlasCount); currentTextureTask = new TextureAtlasFinalizeTask<>(preLoadTask, currentAtlasName, textureManager, progressMonitor, progressToAdd); } break; case "sprite": if (currentTextureTask != null) { transferSpriteData(parser, currentTextureTask); } break; } } else if (currentEvent == XmlPullParser.END_TAG) { String tagName = parser.getName(); if ("atlas".equals(tagName)) { if (currentTextureTask != null) { textureManager.addUpdateTask(currentTextureTask); textureManager.addLoadingTask(currentTextureTask); currentTextureTask = null; } } else if ("atlasList".equals(tagName)) { break; } } currentEvent = parser.nextTag(); } } catch (@Nonnull XmlPullParserException e) { log.error("Failed to load requested texture atlas: {}", atlasName, e); } catch (@Nonnull IOException e) { log.error("Reading error while loading texture atlas: {}", atlasName, e); } finally { done = true; if (xmlStream != null) { try { xmlStream.close(); } catch (@Nonnull IOException ignored) { } } } } private static int getExpectedAtlasCount(@Nonnull XmlPullParser parser, int oldCount) { if (oldCount > 0) { return oldCount; } for (int i = 0; i < parser.getAttributeCount(); i++) { if ("atlasCount".equals(parser.getAttributeName(i))) { try { return Integer.parseInt(parser.getAttributeValue(i)); } catch (@Nonnull NumberFormatException e) { log.warn("Found atlas count entry but failed to parse it."); } break; } } return oldCount; } @Nullable private static String getAtlasTextureName(@Nonnull XmlPullParser parser) { for (int i = 0; i < parser.getAttributeCount(); i++) { if ("file".equals(parser.getAttributeName(i))) { String atlasName = parser.getAttributeValue(i); if (atlasName.endsWith(".png")) { return atlasName.substring(0, atlasName.length() - 4); } return atlasName; } } return null; } private void transferSpriteData( @Nonnull XmlPullParser parser, @Nonnull TextureAtlasFinalizeTask<T> task) { @Nullable String name = null; int posX = -1; int posY = -1; int width = -1; int height = -1; int attributeCount = parser.getAttributeCount(); for (int i = 0; i < attributeCount; i++) { @Nonnull String attributeName = parser.getAttributeName(i); @Nonnull String attributeValue = parser.getAttributeValue(i); try { switch (attributeName) { case "x": posX = Integer.parseInt(attributeValue); break; case "y": posY = Integer.parseInt(attributeValue); break; case "height": height = Integer.parseInt(attributeValue); break; case "width": width = Integer.parseInt(attributeValue); break; case "name": name = attributeValue; break; } } catch (@Nonnull NumberFormatException e) { log.error("Error while parsing texture atlas sprite: {}=\"{}" + '"', attributeName, attributeValue); } } if ((name != null) && (posX > -1) && (posY > -1) && (width > -1) && (height > -1)) { task.addSprite(name, posX, posY, width, height); } else { log.error("Unable to receive all required values for sprite definition!"); } } }