/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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 com.badlogic.gdx.graphics.g3d.particles; import com.badlogic.gdx.assets.AssetDescriptor; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonValue; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.reflect.ClassReflection; import com.badlogic.gdx.utils.reflect.ReflectionException; /** This class handles the assets and configurations required by a given resource when de/serialized. It's handy when a given * object or one of its members requires some assets to be loaded to work properly after being deserialized. To save the assets, * the object should implement the {@link Configurable} interface and obtain a {@link SaveData} object to store every required * asset or information which will be used during the loading phase. The passed in {@link AssetManager} is generally used to find * the asset file name for a given resource of a given type. The class can also store global configurations, this is useful when * dealing with objects which should be allocated once (i.e singleton). The deserialization process must happen in the same order * of serialization, because the per object {@link SaveData} blocks are stored as an {@link Array} within the {@link ResourceData} * , while the global {@link SaveData} instances can be accessed in any order because require a unique {@link String} and are * stored in an {@link ObjectMap}. * @author Inferno */ public class ResourceData<T> implements Json.Serializable { /** This interface must be implemented by any class requiring additional assets to be loaded/saved */ public static interface Configurable<T> { public void save (AssetManager manager, ResourceData<T> resources); public void load (AssetManager manager, ResourceData<T> resources); } /** Contains all the saved data. {@link #data} is a map which link an asset name to its instance. {@link #assets} is an array of * indices addressing a given {@link com.badlogic.gdx.graphics.g3d.particles.ResourceData.AssetData} in the * {@link ResourceData} */ public static class SaveData implements Json.Serializable { ObjectMap<String, Object> data; IntArray assets; private int loadIndex; protected ResourceData resources; public SaveData () { data = new ObjectMap<String, Object>(); assets = new IntArray(); loadIndex = 0; } public SaveData (ResourceData resources) { data = new ObjectMap<String, Object>(); assets = new IntArray(); loadIndex = 0; this.resources = resources; } public <K> void saveAsset (String filename, Class<K> type) { int i = resources.getAssetData(filename, type); if (i == -1) { resources.sharedAssets.add(new AssetData(filename, type)); i = resources.sharedAssets.size - 1; } assets.add(i); } public void save (String key, Object value) { data.put(key, value); } public AssetDescriptor loadAsset () { if (loadIndex == assets.size) return null; AssetData data = (AssetData)resources.sharedAssets.get(assets.get(loadIndex++)); return new AssetDescriptor(data.filename, data.type); } public <K> K load (String key) { return (K)data.get(key); } @Override public void write (Json json) { json.writeValue("data", data, ObjectMap.class); json.writeValue("indices", assets.toArray(), int[].class); } @Override public void read (Json json, JsonValue jsonData) { data = json.readValue("data", ObjectMap.class, jsonData); assets.addAll(json.readValue("indices", int[].class, jsonData)); } } /** This class contains all the information related to a given asset */ public static class AssetData<T> implements Json.Serializable { public String filename; public Class<T> type; public AssetData () { } public AssetData (String filename, Class<T> type) { this.filename = filename; this.type = type; } @Override public void write (Json json) { json.writeValue("filename", filename); json.writeValue("type", type.getName()); } @Override public void read (Json json, JsonValue jsonData) { filename = json.readValue("filename", String.class, jsonData); String className = json.readValue("type", String.class, jsonData); try { type = (Class<T>)ClassReflection.forName(className); } catch (ReflectionException e) { throw new GdxRuntimeException("Class not found: " + className, e); } } } /** Unique data, can be used to save/load generic data which is not always loaded back after saving. Must be used to store data * which is uniquely addressable by a given string (i.e a system configuration). */ private ObjectMap<String, SaveData> uniqueData; /** Objects save data, must be loaded in the same saving order */ private Array<SaveData> data; /** Shared assets among all the configurable objects */ Array<AssetData> sharedAssets; private int currentLoadIndex; public T resource; public ResourceData () { uniqueData = new ObjectMap<String, SaveData>(); data = new Array<SaveData>(true, 3, SaveData.class); sharedAssets = new Array<AssetData>(); currentLoadIndex = 0; } public ResourceData (T resource) { this(); this.resource = resource; } <K> int getAssetData (String filename, Class<K> type) { int i = 0; for (AssetData data : sharedAssets) { if (data.filename.equals(filename) && data.type.equals(type)) { return i; } ++i; } return -1; } public Array<AssetDescriptor> getAssetDescriptors () { Array<AssetDescriptor> descriptors = new Array<AssetDescriptor>(); for (AssetData data : sharedAssets) { descriptors.add(new AssetDescriptor<T>(data.filename, data.type)); } return descriptors; } public Array<AssetData> getAssets () { return sharedAssets; } /** Creates and adds a new SaveData object to the save data list */ public SaveData createSaveData () { SaveData saveData = new SaveData(this); data.add(saveData); return saveData; } /** Creates and adds a new and unique SaveData object to the save data map */ public SaveData createSaveData (String key) { SaveData saveData = new SaveData(this); if (uniqueData.containsKey(key)) throw new RuntimeException("Key already used, data must be unique, use a different key"); uniqueData.put(key, saveData); return saveData; } /** @return the next save data in the list */ public SaveData getSaveData () { return data.get(currentLoadIndex++); } /** @return the unique save data in the map */ public SaveData getSaveData (String key) { return uniqueData.get(key); } @Override public void write (Json json) { json.writeValue("unique", uniqueData, ObjectMap.class); json.writeValue("data", data, Array.class, SaveData.class); json.writeValue("assets", sharedAssets.toArray(AssetData.class), AssetData[].class); json.writeValue("resource", resource, null); } @Override public void read (Json json, JsonValue jsonData) { uniqueData = json.readValue("unique", ObjectMap.class, jsonData); for (Entry<String, SaveData> entry : uniqueData.entries()) { entry.value.resources = this; } data = json.readValue("data", Array.class, SaveData.class, jsonData); for (SaveData saveData : data) { saveData.resources = this; } sharedAssets.addAll(json.readValue("assets", Array.class, AssetData.class, jsonData)); resource = json.readValue("resource", null, jsonData); } }