/* * 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 org.illarion.engine.GameContainer; import org.illarion.engine.graphic.Graphics; import org.illarion.engine.graphic.Scene; import org.illarion.engine.graphic.SceneElement; import org.illarion.engine.graphic.SceneEvent; import org.illarion.engine.graphic.effects.SceneEffect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; /** * This is the abstract implementation of a scene that takes care for the sorting and storing of the scene elements * as this is the same for all the implementations. * * @author Martin Karing <nitram@illarion.org> */ public abstract class AbstractScene<T extends SceneEffect> implements Scene, Comparator<SceneElement> { /** * The logger of this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractScene.class); /** * This list of elements in the scene. This list is kept sorted. */ @Nonnull private final List<SceneElement> sceneElements; /** * This is the queue of events that are published during the updates. */ @Nonnull private final Queue<SceneEvent> eventQueue; /** * The list of effects applied to this scene. */ @Nonnull private final List<T> sceneEffects; /** * This is the snapshot array that is taken and filled shortly before the update calls. Is then used to render * and update the scene. */ @Nonnull private SceneElement[] workingArray = new SceneElement[0]; /** * The amount of elements in the array that are currently valid. */ private int workingArraySize; /** * Create a new scene and setup the internal structures. */ protected AbstractScene() { sceneElements = new ArrayList<>(); eventQueue = new ConcurrentLinkedQueue<>(); sceneEffects = new ArrayList<>(); } @Override public int compare(@Nonnull SceneElement o1, @Nonnull SceneElement o2) { return Integer.compare(o2.getOrder(), o1.getOrder()); } @Override public final void addElement(@Nonnull SceneElement element) { synchronized (sceneElements) { int insertIndex = Collections.binarySearch(sceneElements, element, this); if (insertIndex < 0) { sceneElements.add(-insertIndex - 1, element); } else { sceneElements.add(insertIndex, element); } } } @Override public final void updateElementLocation(@Nonnull SceneElement element) { synchronized (sceneElements) { // If element is not found, insertIndex = (where the element should be added * -1) - 1 int insertIndex = Collections.binarySearch(sceneElements, element, this); // If the item wasn't found, set checkIndex = the proper location int checkIndex = (insertIndex < 0) ? ((insertIndex + 1) * -1) : insertIndex; // If checkIndex is outside our ArrayList, set it to the last element checkIndex = (checkIndex >= sceneElements.size()) ? (checkIndex = sceneElements.size() - 1) : checkIndex; SceneElement testElement = sceneElements.get(checkIndex); if (!Objects.equals(testElement, element)) { removeElement(element); addElement(element); } } } @Override public final void removeElement(@Nonnull SceneElement element) { synchronized (sceneElements) { sceneElements.remove(element); } } /** * This function performs the actual calling of the update functions for all scene elements. * * @param container the game container that is forwarded to the scene elements * @param delta the time since the last update that is reported to the elements */ protected final void updateScene(@Nonnull GameContainer container, int delta) { Arrays.fill(workingArray, null); synchronized (sceneElements) { workingArray = sceneElements.toArray(workingArray); workingArraySize = sceneElements.size(); } @Nullable SceneEvent event = eventQueue.poll(); while (event != null) { boolean notProcessed = true; for (int i = workingArraySize - 1; i >= 0; i--) { SceneElement element = workingArray[i]; if (element.isEventProcessed(container, delta, event)) { notProcessed = false; break; } } if (notProcessed) { event.notHandled(); } event = eventQueue.poll(); } for (int i = 0; i < workingArraySize; i++) { SceneElement element = workingArray[i]; element.update(container, delta); } } /** * This function performs the actual render operation for all elements of the scene. * * @param graphics the graphics instance that is used to render the game */ protected final void renderScene(@Nonnull Graphics graphics) { for (int i = 0; i < workingArraySize; i++) { SceneElement element = workingArray[i]; element.render(graphics); } } @Override public final void publishEvent(@Nonnull SceneEvent event) { eventQueue.offer(event); } @Override public void addEffect(@Nonnull SceneEffect effect) { try { @SuppressWarnings("unchecked") T sceneEffect = (T) effect; if (!sceneEffects.contains(sceneEffect)) { sceneEffects.add(sceneEffect); } } catch (@Nonnull ClassCastException e) { // illegal type } } @Override public void removeEffect(@Nonnull SceneEffect effect) { try { @SuppressWarnings("unchecked") T sceneEffect = (T) effect; sceneEffects.remove(sceneEffect); } catch (@Nonnull ClassCastException e) { // illegal type } } @Override public void clearEffects() { sceneEffects.clear(); } @Override public int getElementCount() { return sceneElements.size(); } /** * Get a scene effect applied to a specific image. * * @param index the index of the effect * @return the scene effect */ protected final T getEffect(int index) { return sceneEffects.get(index); } /** * Get the amount of scene effects allowed. * * @return the scene effects */ protected final int getEffectCount() { return sceneEffects.size(); } }