/* * Copyright 2015 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.assets; import com.google.common.base.Preconditions; import org.terasology.module.sandbox.API; import javax.annotation.concurrent.ThreadSafe; import java.util.Optional; /** * Abstract base class common to all assets. * <p> * An asset is a resource that is used by the game - a texture, sound, block definition and the like. These are typically * loaded from a module, although they can also be created at runtime. Each asset is identified by a ResourceUrn that uniquely * identifies it and can be used to obtain it. This urn provides a lightweight way to serialize a reference to an Asset. * </p> * <p> * Assets are created from a specific type of asset data. There may be a multiple implementations with a common base for a particular type of asset data * - this allows for implementation specific assets (e.g. OpenGL vs DirectX textures for example might have an OpenGLTexture and DirectXTexture implementing class * respectively, with a common Texture base class). * </p> * <p> * Assets may be reloaded by providing a new batch of data, or disposed to free resources - disposed assets may no * longer be used. * </p> * <p> * To support making Asset implementations thread safe reloading, creating an instance and disposal are all synchronized. * Implementations should consider thread safety around any methods they add if it is intended for assets to be used across multiple threads. * </p> * * @author Immortius */ @API @ThreadSafe public abstract class Asset<T extends AssetData> { private final ResourceUrn urn; private final AssetType<?, T> assetType; private volatile boolean disposed; private final DisposalHook disposalHook = new DisposalHook(); /** * The constructor for an asset. It is suggested that implementing classes provide a constructor taking both the urn, and an initial AssetData to load. * * @param urn The urn identifying the asset. * @param assetType The asset type this asset belongs to. */ public Asset(ResourceUrn urn, AssetType<?, T> assetType) { Preconditions.checkNotNull(urn); Preconditions.checkNotNull(assetType); this.urn = urn; this.assetType = assetType; assetType.registerAsset(this, disposalHook); } /** * @return This asset's identifying ResourceUrn. */ public final ResourceUrn getUrn() { return urn; } /** * Retrieves this asset's disposal hook. This is used to hold the action to run when the asset is disposed. * <p> * The action registered to the disposal hook must not have a reference to asset - this would prevent it being garbage collected. It must be a static inner class, * or not contained in the asset class (or an anonymous class defined in a static context). A warning will be logged if this is not the case. * * @return This asset's disposal hook. */ protected final DisposalHook getDisposalHook() { return disposalHook; } /** * Reloads this assets using the new data. * * @param data The data to reload the asset with. * @throws org.terasology.assets.exceptions.InvalidAssetDataException If the asset data is invalid or cannot be loaded */ public final synchronized void reload(T data) { if (!disposed) { doReload(data); } else { throw new IllegalStateException("Cannot reload disposed asset '" + getUrn() + "'"); } } /** * Creates an instance of this asset. The instance will have the same urn as this asset, with the instance flag appended, and initially have the same data and settings. * <p> * Instance assets are reloaded back to the same value as their origin if their asset type is refreshed. * </p> * * @return A new instance of the asset. */ @SuppressWarnings("unchecked") public final <U extends Asset<T>> Optional<U> createInstance() { Preconditions.checkState(!disposed); return (Optional<U>) assetType.createInstance(this); } final synchronized Optional<? extends Asset<T>> createCopy(ResourceUrn copyUrn) { Preconditions.checkState(!disposed); return doCreateCopy(copyUrn, assetType); } /** * Disposes this asset, freeing resources and making it unusable */ public final synchronized void dispose() { if (!disposed) { disposed = true; assetType.onAssetDisposed(this); disposalHook.dispose(); } } /** * Called to reload an asset with the given data. * * @param data The data to load. * @throws org.terasology.assets.exceptions.InvalidAssetDataException If the asset data is invalid or cannot be loaded */ protected abstract void doReload(T data); /** * Attempts to create a copy of the asset, with the given urn. This is used as part of the process of creating an asset instance. * <p> * If direct copies are not supported, then {@link Optional#empty} should be returned. * </p> * <p> * Implementing classes should create a copy of the asset. This may be done by creating an AssetData of the current asset and using it to create * a new asset, or may need to use more implementation specific methods (an OpenGL texture may use an OpenGL texture handle copy technique to produce the * copy, for example) * </p> * * @param copyUrn The urn for the new instance * @param parentAssetType The type of the parent asset * @return The created copy if any */ protected Optional<? extends Asset<T>> doCreateCopy(ResourceUrn copyUrn, AssetType<?, T> parentAssetType) { return Optional.empty(); } /** * @return Whether this asset has been disposed */ public final boolean isDisposed() { return disposed; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Asset) { Asset other = (Asset) obj; return !urn.isInstance() && !other.urn.isInstance() && other.urn.equals(urn); } return false; } @Override public int hashCode() { return urn.hashCode(); } @Override public String toString() { return urn.toString(); } }