package electrosphere.engine.assetmanager; import electrosphere.audio.AudioBuffer; import electrosphere.collision.CollisionBodyCreation; import electrosphere.collision.CollisionEngine; import electrosphere.collision.collidable.Collidable; import electrosphere.engine.Globals; import electrosphere.engine.assetmanager.queue.QueuedAsset; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.actor.ActorShaderMask; import electrosphere.renderer.buffer.HomogenousInstancedArray; import electrosphere.renderer.buffer.HomogenousUniformBuffer; import electrosphere.renderer.loading.ModelLoader; import electrosphere.renderer.model.Mesh; import electrosphere.renderer.model.Model; import electrosphere.renderer.shader.ShaderProgram; import electrosphere.renderer.texture.Texture; import electrosphere.renderer.texture.TextureMap; import electrosphere.server.poseactor.PoseModel; import electrosphere.util.FileUtils; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Semaphore; import org.lwjgl.assimp.AIScene; import org.ode4j.ode.DBody; /** * Manages all assets loaded into the engine including initially loading and destructing */ public class AssetManager { Map modelsLoadedIntoMemory = new ConcurrentHashMap(); List modelsInQueue = new CopyOnWriteArrayList(); List shaderOverrides = new CopyOnWriteArrayList(); Map texturesLoadedIntoMemory = new ConcurrentHashMap(); List texturesInQueue = new CopyOnWriteArrayList(); Map audioLoadedIntoMemory = new ConcurrentHashMap(); List audioInQueue = new CopyOnWriteArrayList(); Map shadersLoadedIntoMemory = new ConcurrentHashMap(); List shadersInQueue = new CopyOnWriteArrayList(); Map physicsMeshesLoadedIntoMemory = new ConcurrentHashMap(); List physicsMeshesToLoad = new CopyOnWriteArrayList(); Map poseModelsLoadedIntoMemory = new ConcurrentHashMap(); List poseModelsInQueue = new CopyOnWriteArrayList(); //A queue of homogenous buffers to allocate this render frame List homogenousBufferAllocationQueue = new CopyOnWriteArrayList(); //A queue of homogenous buffers to allocate this render frame List instanceArrayBufferAllocationQueue = new CopyOnWriteArrayList(); //assets queued to be loaded Semaphore queuedAssetLock = new Semaphore(1); List queuedAssets = new CopyOnWriteArrayList(); // //General asset manager stuff // /** * For each class/type of asset, load all assets in queue */ public void loadAssetsInQueue(){ //models LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load models"); for(String currentPath : modelsInQueue){ modelsInQueue.remove(currentPath); AIScene aiScene = ModelLoader.loadAIScene(currentPath); TextureMap textureMap = null; if(getLocalTextureMapPath(currentPath) != null){ textureMap = TextureMap.construct(getLocalTextureMapPath(currentPath)); } if(aiScene != null){ modelsLoadedIntoMemory.put(currentPath, ModelLoader.createModelFromAiScene(aiScene,textureMap,currentPath)); for(PhysicsMeshQueueItem physicsMeshQueueItem : physicsMeshesToLoad){ if(physicsMeshQueueItem.modelPath.contains(currentPath)){ //create physics physicsMeshesToLoad.remove(physicsMeshQueueItem); physicsMeshesLoadedIntoMemory.put( getCollisionMeshMapKey(physicsMeshQueueItem.collisionEngine,currentPath), CollisionBodyCreation.generateRigidBodyFromAIScene(physicsMeshQueueItem.collisionEngine,aiScene,Collidable.TYPE_STATIC_BIT) ); } } } } //textures from disk to gpu LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load textures"); for(String currentPath : texturesInQueue){ texturesInQueue.remove(currentPath); texturesLoadedIntoMemory.put(currentPath, new Texture(Globals.renderingEngine.getOpenGLState(), currentPath)); } //audio from disk LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load audio"); if(Globals.audioEngine != null && Globals.audioEngine.initialized()){ for(String currentPath : audioInQueue){ audioInQueue.remove(currentPath); audioLoadedIntoMemory.put(currentPath, new AudioBuffer(currentPath)); } } //shaders LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load shaders"); for(ActorShaderMask currentShader : shadersInQueue){ shadersInQueue.remove(currentShader); String key = getShaderKey(currentShader.getVertexShaderPath(),currentShader.getGeometryShaderPath(),currentShader.getFragmentShaderPath()); if(currentShader.getGeometryShaderPath() == null){ shadersLoadedIntoMemory.put( key, ShaderProgram.loadSpecificShader(currentShader.getVertexShaderPath(),currentShader.getFragmentShaderPath()) ); } else { shadersLoadedIntoMemory.put( key, ShaderProgram.loadSpecificShader(currentShader.getVertexShaderPath(),currentShader.getGeometryShaderPath(),currentShader.getFragmentShaderPath()) ); } } //pose models LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load pose models"); for(String currentPath: poseModelsInQueue){ poseModelsInQueue.remove(currentPath); AIScene scene = ModelLoader.loadAIScene(currentPath); poseModelsLoadedIntoMemory.put(currentPath, new PoseModel(currentPath, scene)); } //queued assets LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load queued assets"); queuedAssetLock.acquireUninterruptibly(); for(QueuedAsset queuedAsset : queuedAssets){ queuedAsset.load(); } queuedAssets.clear(); queuedAssetLock.release(); //allocate homogenous buffers LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Allocate homogenous buffers"); allocateHomogenousBuffers(); //allocate instance array buffers LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Allocate instance array buffers"); allocateInstanceArrayBuffers(); //override meshes LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Override meshes"); performMeshOverrides(); } // //Models // public void addModelPathToQueue(String path){ if(!modelsInQueue.contains(path) && !modelsLoadedIntoMemory.containsKey(path)){ modelsInQueue.add(path); } } public Model fetchModel(String path){ Model rVal = null; if(modelsLoadedIntoMemory.containsKey(path)){ rVal = modelsLoadedIntoMemory.get(path); } return rVal; } /** Registers a (presumably generated in code) model with the asset manager @returns a random string that represents the model in the asset manager */ public String registerModel(Model m){ String rVal; UUID newUUID = UUID.randomUUID(); rVal = newUUID.toString(); modelsLoadedIntoMemory.put(rVal,m); return rVal; } /** * Registers a (presumably generated in code) model to a given path in the asset manager * Used particularly if you have a specific path you want to relate to a specific model (eg terrain chunk code) * @param m The model to register * @param path The path to register the model to */ public void registerModelWithPath(Model m, String path){ modelsLoadedIntoMemory.put(path, m); } public void registerModelToSpecificString(Model m, String s){ modelsLoadedIntoMemory.put(s,m); } public void deregisterModelPath(String path){ modelsLoadedIntoMemory.remove(path); } public void queueOverrideMeshShader(String modelName, String meshName, String vertPath, String fragPath){ MeshShaderOverride override = new MeshShaderOverride(modelName,meshName,vertPath,fragPath); shaderOverrides.add(override); } public void performMeshOverrides(){ List toRemove = new LinkedList(); for(MeshShaderOverride shaderOverride : shaderOverrides){ Model model = null; if((model = fetchModel(shaderOverride.modelName)) != null){ for(Mesh mesh : model.getMeshes()){ if(mesh.getMeshName().equals(shaderOverride.getMeshName())){ mesh.setShader(ShaderProgram.loadSpecificShader(shaderOverride.vertPath, shaderOverride.fragPath)); } } toRemove.add(shaderOverride); } } for(MeshShaderOverride shaderOverride : toRemove){ shaderOverrides.remove(shaderOverride); } } /** * Nuclear function, reloads all shaders loaded into memory */ public void forceReloadAllModels(){ for(String modelKey : modelsLoadedIntoMemory.keySet()){ if(modelKey.contains("Models")){ modelsInQueue.add(modelKey); modelsLoadedIntoMemory.remove(modelKey); } } // modelsLoadedIntoMemory.clear(); } // //Pose Models // /** * Adds a pose model to the list of pose models to load * @param path The path to load */ public void addPoseModelPathToQueue(String path){ if(!poseModelsInQueue.contains(path) && !poseModelsLoadedIntoMemory.containsKey(path)){ poseModelsInQueue.add(path); } } /** * Fetches a pose model * @param path The path to fetch * @return The pose model if it exists, null otherwise */ public PoseModel fetchPoseModel(String path){ PoseModel rVal = null; if(poseModelsLoadedIntoMemory.containsKey(path)){ rVal = poseModelsLoadedIntoMemory.get(path); } return rVal; } // // Textures // public void addTexturePathtoQueue(String path){ if(!texturesInQueue.contains(path) && !texturesLoadedIntoMemory.containsKey(path)){ texturesInQueue.add(path); } } public Texture fetchTexture(String path){ Texture rVal = null; if(texturesLoadedIntoMemory.containsKey(path)){ rVal = texturesLoadedIntoMemory.get(path); } return rVal; } public String registerTexture(Texture t){ String rVal; UUID newUUID = UUID.randomUUID(); rVal = newUUID.toString(); texturesLoadedIntoMemory.put(rVal,t); return rVal; } public boolean hasLoadedTexture(String path){ return texturesLoadedIntoMemory.containsKey(path); } /** * Gets a local texture map's path from a model's path * @param modelPath The model's path */ private String getLocalTextureMapPath(String modelPath){ File modelFile = FileUtils.getAssetFile(modelPath); File containingDirectory = modelFile.getParentFile(); File[] children = containingDirectory.listFiles(); if(children != null){ for(File child : children){ if(child.getName().equals("texturemap.json")){ String rVal = child.getPath(); String fixed = rVal.replace(".\\assets", "").replace("./assets", ""); return fixed; } } } return null; } // //AUDIO // public void addAudioPathToQueue(String path){ String sanitizedPath = FileUtils.sanitizeFilePath(path); if(!audioInQueue.contains(sanitizedPath) && !audioLoadedIntoMemory.containsKey(sanitizedPath)){ audioInQueue.add(sanitizedPath); } } public AudioBuffer fetchAudio(String path){ AudioBuffer rVal = null; String sanitizedPath = FileUtils.sanitizeFilePath(path); if(audioLoadedIntoMemory.containsKey(sanitizedPath)){ rVal = audioLoadedIntoMemory.get(sanitizedPath); } else { LoggerInterface.loggerAudio.WARNING("Failed to find audio " + sanitizedPath); } return rVal; } /** * Gets all audio loaded into the engine * @return The collection of all audio buffers */ public Collection getAllAudio(){ return Collections.unmodifiableCollection(this.audioLoadedIntoMemory.values()); } // //SHADERS // public void addShaderToQueue(String vertexShader, String fragmentShader){ shadersInQueue.add(new ActorShaderMask("","",vertexShader,null,fragmentShader)); } public void addShaderToQueue(String vertexShader, String geometryShader, String fragmentShader){ shadersInQueue.add(new ActorShaderMask("","",vertexShader,geometryShader,fragmentShader)); } public ShaderProgram fetchShader(String vertexPath, String geometryShader, String fragmentPath){ String path = getShaderKey(vertexPath,geometryShader,fragmentPath); ShaderProgram rVal = null; if(shadersLoadedIntoMemory.containsKey(path)){ rVal = shadersLoadedIntoMemory.get(path); } return rVal; } static String getShaderKey(String vertexPath, String geometryPath, String fragmentPath){ return vertexPath + "-" + geometryPath + "-" + fragmentPath; } /** * Nuclear function, reloads all shaders loaded into memory */ public void forceReloadAllShaders(){ for(String shaderKey : shadersLoadedIntoMemory.keySet()){ String shaderPaths[] = shaderKey.split("-"); if(shaderPaths[1].equals("null")){ shaderPaths[1] = null; } shadersInQueue.add(new ActorShaderMask("","",shaderPaths[0],shaderPaths[1],shaderPaths[2])); } shadersLoadedIntoMemory.clear(); } // //COLLISION MESH // public void addCollisionMeshToQueue(PhysicsMeshQueueItem physicsMeshQueueItem){ if( !physicsMeshesToLoad.contains(physicsMeshQueueItem) && !physicsMeshesLoadedIntoMemory.containsKey(getCollisionMeshMapKey( physicsMeshQueueItem.collisionEngine, physicsMeshQueueItem.modelPath ))){ physicsMeshesToLoad.add(physicsMeshQueueItem); } } public DBody fetchCollisionObject(CollisionEngine collisionEngine, String path){ return physicsMeshesLoadedIntoMemory.get(getCollisionMeshMapKey(collisionEngine,path)); } /** * Gets a key based on collision engine object hash and path of model * @param collisionEngine collision engine * @param path The path * @return The key */ private String getCollisionMeshMapKey(CollisionEngine collisionEngine, String path){ return collisionEngine + path; } // //HOMOGENOUS UNIFORM BUFFERS // /** * Allocates all uniform buffers in queue */ public void allocateHomogenousBuffers(){ for(HomogenousUniformBuffer buffer : homogenousBufferAllocationQueue){ buffer.allocate(); } homogenousBufferAllocationQueue.clear(); } /** * Adds a uniform buffer to the queue to be allocated * @param buffer The buffer */ public void addHomogenousBufferToQueue(HomogenousUniformBuffer buffer){ homogenousBufferAllocationQueue.add(buffer); } // //INSTANCE ARRAY BUFFERS // /** * Allocates all instance array buffers in queue */ public void allocateInstanceArrayBuffers(){ for(HomogenousInstancedArray buffer : instanceArrayBufferAllocationQueue){ buffer.allocate(); } instanceArrayBufferAllocationQueue.clear(); } /** * Adds an instance array buffer to the queue to be allocated * @param buffer The buffer */ public void addInstanceArrayBufferToQueue(HomogenousInstancedArray buffer){ instanceArrayBufferAllocationQueue.add(buffer); } // //Async generic queued assets // /** * Queues an asset to be loaded on the main thread * @param asset the asset */ public void queuedAsset(QueuedAsset asset){ queuedAssetLock.acquireUninterruptibly(); this.queuedAssets.add(asset); queuedAssetLock.release(); } }