diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 7e644b3c..351b2d5f 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1654,6 +1654,7 @@ Refactor recast pathfinding classes (05/03/2025) Fix voxel pathfinding logic +Remove several usages of concurrent datastructures diff --git a/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java b/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java index 11a3218e..c659e15c 100644 --- a/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java +++ b/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java @@ -1,7 +1,8 @@ package electrosphere.client.scene; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import org.joml.Vector3d; import org.ode4j.ode.DContactGeom; @@ -24,11 +25,11 @@ import electrosphere.logger.LoggerInterface; public class ClientSceneWrapper { //entity id translation between server/client - Map clientToServerIdMap = new ConcurrentHashMap(); - Map serverToClientIdMap = new ConcurrentHashMap(); + Map clientToServerIdMap = new HashMap(); + Map serverToClientIdMap = new HashMap(); //The list of server IDs that have been deleted - Map deletedServerIds = new ConcurrentHashMap(); + Map deletedServerIds = new HashMap(); //The scene backing the wrapper Scene scene; @@ -49,6 +50,11 @@ public class ClientSceneWrapper { //The hitbox manager HitboxManager hitboxManager; + /** + * Lock for threadsafing the scene wrapper + */ + ReentrantLock lock = new ReentrantLock(); + /** * Constructor * @param scene The scene @@ -71,8 +77,10 @@ public class ClientSceneWrapper { */ public void mapIdToId(int clientId, int serverId){ LoggerInterface.loggerNetworking.DEBUG("[CLIENT] MapID: " + clientId + " <===> " + serverId); + lock.lock(); clientToServerIdMap.put(clientId, serverId); serverToClientIdMap.put(serverId, clientId); + lock.unlock(); } /** @@ -81,11 +89,15 @@ public class ClientSceneWrapper { * @return The equivalent id on the server, or -1 if no equivalent is found */ public int mapClientToServerId(int clientId){ + lock.lock(); if(clientToServerIdMap.get(clientId) == null){ LoggerInterface.loggerNetworking.ERROR(new Error("Failed to map client entity " + clientId + " to server entity!")); + lock.unlock(); return -1; } - return clientToServerIdMap.get(clientId); + int rVal = clientToServerIdMap.get(clientId); + lock.unlock(); + return rVal; } /** @@ -94,7 +106,10 @@ public class ClientSceneWrapper { * @return The equivalent id on the client */ public int mapServerToClientId(int serverId){ - return serverToClientIdMap.get(serverId); + lock.lock(); + int rVal = serverToClientIdMap.get(serverId); + lock.unlock(); + return rVal; } /** @@ -103,7 +118,10 @@ public class ClientSceneWrapper { * @return true if the map contains that id, false otherwise */ public boolean containsServerId(int serverId){ - return serverToClientIdMap.containsKey(serverId); + lock.lock(); + boolean rVal = serverToClientIdMap.containsKey(serverId); + lock.unlock(); + return rVal; } @@ -113,7 +131,10 @@ public class ClientSceneWrapper { * @return True if the server->client map contains the provided id */ public boolean serverToClientMapContainsId(int id){ - return serverToClientIdMap.containsKey(id); + lock.lock(); + boolean rVal = serverToClientIdMap.containsKey(id); + lock.unlock(); + return rVal; } /** @@ -122,7 +143,10 @@ public class ClientSceneWrapper { * @return true if there's a corresponding server id, false otherwise */ public boolean clientToServerMapContainsId(int id){ - return clientToServerIdMap.containsKey(id); + lock.lock(); + boolean rVal = clientToServerIdMap.containsKey(id); + lock.unlock(); + return rVal; } /** @@ -130,6 +154,7 @@ public class ClientSceneWrapper { * @param clientEntity The client entity */ public void deregisterTranslationMapping(Entity clientEntity){ + lock.lock(); if(this.clientToServerMapContainsId(clientEntity.getId())){ //remove from client->server map int serverId = clientToServerIdMap.remove(clientEntity.getId()); @@ -138,6 +163,7 @@ public class ClientSceneWrapper { deletedServerIds.put(serverId,true); LoggerInterface.loggerNetworking.DEBUG("[CLIENT] Remove scene from client<->server translation layer: " + clientEntity.getId() + "<->" + serverId); } + lock.unlock(); } /** @@ -146,7 +172,10 @@ public class ClientSceneWrapper { * @return true if it was registered at one point and has since been deleted, false otherwise */ public boolean hasBeenDeleted(int serverId){ - return deletedServerIds.containsKey(serverId); + lock.lock(); + boolean rVal = deletedServerIds.containsKey(serverId); + lock.unlock(); + return rVal; } @@ -156,22 +185,26 @@ public class ClientSceneWrapper { * @return The entity in question */ public Entity getEntityFromServerId(int id){ + Entity rVal = null; + lock.lock(); if(serverToClientIdMap.containsKey(id)){ int clientId = mapServerToClientId(id); - return scene.getEntityFromId(clientId); - } else { - return null; + rVal = scene.getEntityFromId(clientId); } + lock.unlock(); + return rVal; } /** * Dumps the status of the network translation layer */ public void dumpTranslationLayerStatus(){ + lock.lock(); LoggerInterface.loggerNetworking.WARNING("Client -> Server keys"); LoggerInterface.loggerNetworking.WARNING(clientToServerIdMap.keySet() + ""); LoggerInterface.loggerNetworking.WARNING("Server -> Client keys"); LoggerInterface.loggerNetworking.WARNING(serverToClientIdMap.keySet() + ""); + lock.unlock(); } /** @@ -179,6 +212,7 @@ public class ClientSceneWrapper { * @param id The id */ public void dumpIdData(int id){ + lock.lock(); LoggerInterface.loggerNetworking.WARNING("Offending ID " + id); LoggerInterface.loggerNetworking.WARNING("Client->Server Map contains? " + clientToServerIdMap.containsKey(id)); LoggerInterface.loggerNetworking.WARNING("Server->Client Map contains? " + serverToClientIdMap.containsKey(id)); @@ -188,6 +222,7 @@ public class ClientSceneWrapper { if(serverToClientIdMap.containsKey(id)){ LoggerInterface.loggerNetworking.WARNING("Server->Client Map entity: " + serverToClientIdMap.get(id)); } + lock.unlock(); } /** diff --git a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java index cd0df053..8b1ff991 100644 --- a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java +++ b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java @@ -24,12 +24,11 @@ import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; 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.locks.ReentrantLock; import org.lwjgl.assimp.AIScene; @@ -39,44 +38,48 @@ 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 modelsInDeleteQueue = new CopyOnWriteArrayList(); - List shaderOverrides = new CopyOnWriteArrayList(); - - Map texturesLoadedIntoMemory = new ConcurrentHashMap(); - List texturesInQueue = new CopyOnWriteArrayList(); - List texturesInDeleteQueue = new CopyOnWriteArrayList(); - - Map audioLoadedIntoMemory = new ConcurrentHashMap(); - List audioInQueue = new CopyOnWriteArrayList(); - Map shadersLoadedIntoMemory = new ConcurrentHashMap(); - List shadersInQueue = new CopyOnWriteArrayList(); + /** + * Lock for thread-safing the work + */ + ReentrantLock lock = new ReentrantLock(); + + Map modelsLoadedIntoMemory = new HashMap(); + List modelsInQueue = new LinkedList(); + List modelsInDeleteQueue = new LinkedList(); + List shaderOverrides = new LinkedList(); + + Map texturesLoadedIntoMemory = new HashMap(); + List texturesInQueue = new LinkedList(); + List texturesInDeleteQueue = new LinkedList(); + + Map audioLoadedIntoMemory = new HashMap(); + List audioInQueue = new LinkedList(); + + Map shadersLoadedIntoMemory = new HashMap(); + List shadersInQueue = new LinkedList(); // //Compute shader related // - Map computeShadersLoadedIntoMemory = new ConcurrentHashMap(); - List computeShadersInQueue = new CopyOnWriteArrayList(); + Map computeShadersLoadedIntoMemory = new HashMap(); + List computeShadersInQueue = new LinkedList(); - Map physicsMeshesLoadedIntoMemory = new ConcurrentHashMap(); - List physicsMeshesToLoad = new CopyOnWriteArrayList(); + Map physicsMeshesLoadedIntoMemory = new HashMap(); + List physicsMeshesToLoad = new LinkedList(); - Map poseModelsLoadedIntoMemory = new ConcurrentHashMap(); - List poseModelsInQueue = new CopyOnWriteArrayList(); - List poseModelsInDeleteQueue = new CopyOnWriteArrayList(); + Map poseModelsLoadedIntoMemory = new HashMap(); + List poseModelsInQueue = new LinkedList(); + List poseModelsInDeleteQueue = new LinkedList(); //A queue of homogenous buffers to allocate this render frame - List homogenousBufferAllocationQueue = new CopyOnWriteArrayList(); + List homogenousBufferAllocationQueue = new LinkedList(); //A queue of homogenous buffers to allocate this render frame - List instanceArrayBufferAllocationQueue = new CopyOnWriteArrayList(); + List instanceArrayBufferAllocationQueue = new LinkedList(); //assets queued to be loaded - ReentrantLock queuedAssetLock = new ReentrantLock(); List> queuedAssets = new LinkedList>(); /** @@ -98,8 +101,8 @@ public class AssetManager { //models LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load models"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load Models"); + lock.lock(); for(String currentPath : modelsInQueue){ - modelsInQueue.remove(currentPath); AIScene aiScene = ModelLoader.loadAIScene(currentPath); TextureMap textureMap = null; if(this.getLocalTextureMapPath(currentPath) != null){ @@ -119,43 +122,43 @@ public class AssetManager { } } } + modelsInQueue.clear(); Globals.profiler.endCpuSample(); //textures from disk to gpu LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load textures"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load textures"); for(String currentPath : texturesInQueue){ - texturesInQueue.remove(currentPath); Texture tex = new Texture(Globals.renderingEngine.getOpenGLState(), currentPath); texturesLoadedIntoMemory.put(currentPath, tex); } + texturesInQueue.clear(); Globals.profiler.endCpuSample(); //audio from disk LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load audio"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load audio"); if(Globals.audioEngine != null && Globals.audioEngine.initialized()){ for(String currentPath : audioInQueue){ - audioInQueue.remove(currentPath); audioLoadedIntoMemory.put(currentPath, new AudioBuffer(currentPath)); } + audioInQueue.clear(); } Globals.profiler.endCpuSample(); //shaders LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load visual shaders"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load visual shaders"); for(ActorShaderMask currentShader : shadersInQueue){ - shadersInQueue.remove(currentShader); String key = getShaderKey(currentShader.getVertexShaderPath(),currentShader.getFragmentShaderPath()); shadersLoadedIntoMemory.put( key, VisualShader.loadSpecificShader(currentShader.getVertexShaderPath(),currentShader.getFragmentShaderPath()) ); } + shadersInQueue.clear(); Globals.profiler.endCpuSample(); //compute shaders LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load compute shaders"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load compute shaders"); for(String computePath : computeShadersInQueue){ - computeShadersInQueue.remove(computePath); String key = getComputeShaderKey(computePath); try { computeShadersLoadedIntoMemory.put( @@ -166,20 +169,20 @@ public class AssetManager { e.printStackTrace(); } } + computeShadersInQueue.clear(); Globals.profiler.endCpuSample(); //pose models LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load pose models"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load pose models"); for(String currentPath: poseModelsInQueue){ - poseModelsInQueue.remove(currentPath); AIScene scene = ModelLoader.loadAIScene(currentPath); poseModelsLoadedIntoMemory.put(currentPath, new PoseModel(currentPath, scene)); } + poseModelsInQueue.clear(); Globals.profiler.endCpuSample(); //queued assets LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Load queued assets"); Globals.profiler.beginCpuSample("AssetManager.loadAssetsInQueue - Load queued assets"); - queuedAssetLock.lock(); if(queuedAssets.size() > MAX_ASSETS_PER_FRAME){ for(int i = 0; i < MAX_ASSETS_PER_FRAME; i++){ QueuedAsset queuedAsset = queuedAssets.remove(0); @@ -201,7 +204,6 @@ public class AssetManager { } queuedAssets.clear(); } - queuedAssetLock.unlock(); Globals.profiler.endCpuSample(); //allocate homogenous buffers @@ -219,6 +221,8 @@ public class AssetManager { //override meshes LoggerInterface.loggerEngine.DEBUG_LOOP("AssetManager - Override meshes"); this.performMeshOverrides(); + + lock.unlock(); } @@ -228,6 +232,7 @@ public class AssetManager { public void updateAsset(String path){ LoggerInterface.loggerEngine.DEBUG("AssetManager - updateAsset"); //models + lock.lock(); if(modelsLoadedIntoMemory.containsKey(path)){ this.queueModelForDeletion(path); this.deleteModelsInDeleteQueue(); @@ -254,15 +259,18 @@ public class AssetManager { if(computeShadersLoadedIntoMemory.containsKey(path)){ throw new Error("Unhandled asset type! (Shader - Compute)"); } + lock.unlock(); } /** * Handles the delete queues */ public void handleDeleteQueue(){ + lock.lock(); this.deleteModelsInDeleteQueue(); this.deletePoseModelsInDeleteQueue(); this.deleteTexturesInDeleteQueue(); + lock.unlock(); } @@ -277,16 +285,20 @@ public class AssetManager { // public void addModelPathToQueue(String path){ + lock.lock(); if(!modelsInQueue.contains(path) && !modelsLoadedIntoMemory.containsKey(path)){ modelsInQueue.add(path); } + lock.unlock(); } public Model fetchModel(String path){ Model rVal = null; + lock.lock(); if(modelsLoadedIntoMemory.containsKey(path)){ rVal = modelsLoadedIntoMemory.get(path); } + lock.unlock(); return rVal; } @@ -295,7 +307,9 @@ public class AssetManager { * @param modelPath The path to the model */ public void queueModelForDeletion(String modelPath){ + lock.lock(); modelsInDeleteQueue.add(modelPath); + lock.unlock(); } /** @@ -306,7 +320,9 @@ public class AssetManager { String rVal; UUID newUUID = UUID.randomUUID(); rVal = newUUID.toString(); + lock.lock(); modelsLoadedIntoMemory.put(rVal,m); + lock.unlock(); return rVal; } @@ -317,20 +333,27 @@ public class AssetManager { * @param path The path to register the model to */ public void registerModelWithPath(Model m, String path){ + lock.lock(); modelsLoadedIntoMemory.put(path, m); + lock.unlock(); } public void deregisterModelPath(String path){ + lock.lock(); modelsLoadedIntoMemory.remove(path); + lock.unlock(); } public void queueOverrideMeshShader(String modelName, String meshName, String vertPath, String fragPath){ + lock.lock(); MeshShaderOverride override = new MeshShaderOverride(modelName,meshName,vertPath,fragPath); shaderOverrides.add(override); + lock.unlock(); } public void performMeshOverrides(){ List toRemove = new LinkedList(); + lock.lock(); for(MeshShaderOverride shaderOverride : shaderOverrides){ Model model = null; if((model = fetchModel(shaderOverride.modelName)) != null){ @@ -345,25 +368,28 @@ public class AssetManager { for(MeshShaderOverride shaderOverride : toRemove){ shaderOverrides.remove(shaderOverride); } + lock.unlock(); } /** * Nuclear function, reloads all shaders loaded into memory */ public void forceReloadAllModels(){ + lock.lock(); for(String modelKey : modelsLoadedIntoMemory.keySet()){ if(modelKey.contains("Models")){ modelsInQueue.add(modelKey); modelsLoadedIntoMemory.remove(modelKey); } } - // modelsLoadedIntoMemory.clear(); + lock.unlock(); } /** * Deletes all models in the delete queue */ public void deleteModelsInDeleteQueue(){ + lock.lock(); for(String modelPath : modelsInDeleteQueue){ Model model = this.fetchModel(modelPath); if(model != null){ @@ -371,6 +397,7 @@ public class AssetManager { } this.modelsLoadedIntoMemory.remove(modelPath); } + lock.unlock(); } @@ -388,9 +415,11 @@ public class AssetManager { * @param path The path to load */ public void addPoseModelPathToQueue(String path){ + lock.lock(); if(!poseModelsInQueue.contains(path) && !poseModelsLoadedIntoMemory.containsKey(path)){ poseModelsInQueue.add(path); } + lock.unlock(); } /** @@ -400,9 +429,11 @@ public class AssetManager { */ public PoseModel fetchPoseModel(String path){ PoseModel rVal = null; + lock.lock(); if(poseModelsLoadedIntoMemory.containsKey(path)){ rVal = poseModelsLoadedIntoMemory.get(path); } + lock.unlock(); return rVal; } @@ -413,7 +444,9 @@ public class AssetManager { * @param path The path to register the pose model to */ public void registerPoseModelWithPath(PoseModel m, String path){ + lock.lock(); poseModelsLoadedIntoMemory.put(path, m); + lock.unlock(); } /** @@ -421,13 +454,16 @@ public class AssetManager { * @param modelPath The path to the pose model */ public void queuePoseModelForDeletion(String modelPath){ + lock.lock(); poseModelsInDeleteQueue.add(modelPath); + lock.unlock(); } /** * Deletes all pose models in the delete queue */ public void deletePoseModelsInDeleteQueue(){ + lock.lock(); for(String modelPath : poseModelsInDeleteQueue){ PoseModel poseModel = this.fetchPoseModel(modelPath); if(poseModel != null){ @@ -435,6 +471,7 @@ public class AssetManager { } this.poseModelsLoadedIntoMemory.remove(modelPath); } + lock.unlock(); } @@ -451,9 +488,11 @@ public class AssetManager { public void addTexturePathtoQueue(String path){ + lock.lock(); if(!texturesInQueue.contains(path) && !texturesLoadedIntoMemory.containsKey(path)){ texturesInQueue.add(path); } + lock.unlock(); } /** @@ -461,14 +500,18 @@ public class AssetManager { * @param texturePath The path to the texture */ public void queueTextureForDeletion(String texturePath){ + lock.lock(); texturesInDeleteQueue.add(texturePath); + lock.unlock(); } public Texture fetchTexture(String path){ Texture rVal = null; + lock.lock(); if(texturesLoadedIntoMemory.containsKey(path)){ rVal = texturesLoadedIntoMemory.get(path); } + lock.unlock(); return rVal; } @@ -476,7 +519,9 @@ public class AssetManager { String rVal; UUID newUUID = UUID.randomUUID(); rVal = newUUID.toString(); + lock.lock(); texturesLoadedIntoMemory.put(rVal,t); + lock.unlock(); return rVal; } @@ -486,11 +531,16 @@ public class AssetManager { * @param path The path */ public void registerTextureToPath(Texture t, String path){ + lock.lock(); texturesLoadedIntoMemory.put(path,t); + lock.unlock(); } public boolean hasLoadedTexture(String path){ - return texturesLoadedIntoMemory.containsKey(path); + lock.lock(); + boolean rVal = texturesLoadedIntoMemory.containsKey(path); + lock.unlock(); + return rVal; } /** @@ -498,6 +548,7 @@ public class AssetManager { * @param modelPath The model's path */ private String getLocalTextureMapPath(String modelPath){ + lock.lock(); File modelFile = FileUtils.getAssetFile(modelPath); File containingDirectory = modelFile.getParentFile(); File[] children = containingDirectory.listFiles(); @@ -506,10 +557,12 @@ public class AssetManager { if(child.getName().equals("texturemap.json")){ String rVal = child.getPath(); String fixed = rVal.replace(".\\assets", "").replace("./assets", ""); + lock.unlock(); return fixed; } } } + lock.unlock(); return null; } @@ -517,6 +570,7 @@ public class AssetManager { * Deletes all textures in the delete queue */ public void deleteTexturesInDeleteQueue(){ + lock.lock(); for(String texturePath : texturesInDeleteQueue){ Texture texture = this.fetchTexture(texturePath); if(texture != null){ @@ -524,6 +578,7 @@ public class AssetManager { } this.texturesLoadedIntoMemory.remove(texturePath); } + lock.unlock(); } @@ -539,13 +594,16 @@ public class AssetManager { // public void addAudioPathToQueue(String path){ + lock.lock(); String sanitizedPath = FileUtils.sanitizeFilePath(path); if(!audioInQueue.contains(sanitizedPath) && !audioLoadedIntoMemory.containsKey(sanitizedPath)){ audioInQueue.add(sanitizedPath); } + lock.unlock(); } public AudioBuffer fetchAudio(String path){ + lock.lock(); AudioBuffer rVal = null; String sanitizedPath = FileUtils.sanitizeFilePath(path); if(audioLoadedIntoMemory.containsKey(sanitizedPath)){ @@ -553,6 +611,7 @@ public class AssetManager { } else { LoggerInterface.loggerAudio.WARNING("Failed to find audio " + sanitizedPath); } + lock.unlock(); return rVal; } @@ -561,7 +620,10 @@ public class AssetManager { * @return The collection of all audio buffers */ public Collection getAllAudio(){ - return Collections.unmodifiableCollection(this.audioLoadedIntoMemory.values()); + lock.lock(); + Collection rVal = Collections.unmodifiableCollection(this.audioLoadedIntoMemory.values()); + lock.unlock(); + return rVal; } @@ -575,15 +637,19 @@ public class AssetManager { //SHADERS // public void addShaderToQueue(String vertexShader, String fragmentShader){ + lock.lock(); shadersInQueue.add(new ActorShaderMask("","",vertexShader,fragmentShader)); + lock.unlock(); } public VisualShader fetchShader(String vertexPath, String fragmentPath){ + lock.lock(); String path = getShaderKey(vertexPath,fragmentPath); VisualShader rVal = null; if(shadersLoadedIntoMemory.containsKey(path)){ rVal = shadersLoadedIntoMemory.get(path); } + lock.unlock(); return rVal; } @@ -595,11 +661,13 @@ public class AssetManager { * Nuclear function, reloads all shaders loaded into memory */ public void forceReloadAllShaders(){ + lock.lock(); for(String shaderKey : shadersLoadedIntoMemory.keySet()){ String shaderPaths[] = shaderKey.split("-"); shadersInQueue.add(new ActorShaderMask("","",shaderPaths[0],shaderPaths[1])); } shadersLoadedIntoMemory.clear(); + lock.unlock(); } // @@ -610,7 +678,9 @@ public class AssetManager { * @param computePath The path to the source code for the shader */ public void addComputeShaderToQueue(String computePath){ + lock.lock(); computeShadersInQueue.add(getComputeShaderKey(computePath)); + lock.unlock(); } /** @@ -619,11 +689,13 @@ public class AssetManager { * @return The compute shader if it exists, null otherwise */ public ComputeShader fetchComputeShader(String computePath){ + lock.lock(); String key = getComputeShaderKey(computePath); ComputeShader rVal = null; if(computeShadersLoadedIntoMemory.containsKey(key)){ rVal = computeShadersLoadedIntoMemory.get(key); } + lock.unlock(); return rVal; } @@ -643,6 +715,7 @@ public class AssetManager { //COLLISION MESH // public void addCollisionMeshToQueue(PhysicsMeshQueueItem physicsMeshQueueItem){ + lock.lock(); if( !physicsMeshesToLoad.contains(physicsMeshQueueItem) && !physicsMeshesLoadedIntoMemory.containsKey(getCollisionMeshMapKey( @@ -651,10 +724,14 @@ public class AssetManager { ))){ physicsMeshesToLoad.add(physicsMeshQueueItem); } + lock.unlock(); } public DBody fetchCollisionObject(CollisionEngine collisionEngine, String path){ - return physicsMeshesLoadedIntoMemory.get(getCollisionMeshMapKey(collisionEngine,path)); + lock.lock(); + DBody rVal = physicsMeshesLoadedIntoMemory.get(getCollisionMeshMapKey(collisionEngine,path)); + lock.unlock(); + return rVal; } /** @@ -675,10 +752,12 @@ public class AssetManager { * Allocates all uniform buffers in queue */ public void allocateHomogenousBuffers(){ + lock.lock(); for(HomogenousUniformBuffer buffer : homogenousBufferAllocationQueue){ buffer.allocate(); } homogenousBufferAllocationQueue.clear(); + lock.unlock(); } /** @@ -686,7 +765,9 @@ public class AssetManager { * @param buffer The buffer */ public void addHomogenousBufferToQueue(HomogenousUniformBuffer buffer){ + lock.lock(); homogenousBufferAllocationQueue.add(buffer); + lock.unlock(); } @@ -697,10 +778,12 @@ public class AssetManager { * Allocates all instance array buffers in queue */ public void allocateInstanceArrayBuffers(){ + lock.lock(); for(HomogenousInstancedArray buffer : instanceArrayBufferAllocationQueue){ buffer.allocate(); } instanceArrayBufferAllocationQueue.clear(); + lock.unlock(); } /** @@ -708,7 +791,9 @@ public class AssetManager { * @param buffer The buffer */ public void addInstanceArrayBufferToQueue(HomogenousInstancedArray buffer){ + lock.lock(); instanceArrayBufferAllocationQueue.add(buffer); + lock.unlock(); } @@ -720,7 +805,7 @@ public class AssetManager { * @param asset the asset */ public String queuedAsset(QueuedAsset asset){ - queuedAssetLock.lock(); + lock.lock(); this.queuedAssets.add(asset); //promise a specific string for this asset @@ -740,7 +825,7 @@ public class AssetManager { asset.setPromisedPath(promisedPath); } - queuedAssetLock.unlock(); + lock.unlock(); return promisedPath; } diff --git a/src/main/java/electrosphere/engine/signal/SignalSystem.java b/src/main/java/electrosphere/engine/signal/SignalSystem.java index e61b10a0..d9f7b5b6 100644 --- a/src/main/java/electrosphere/engine/signal/SignalSystem.java +++ b/src/main/java/electrosphere/engine/signal/SignalSystem.java @@ -1,10 +1,10 @@ package electrosphere.engine.signal; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; import electrosphere.engine.service.Service; import electrosphere.engine.signal.Signal.SignalType; @@ -21,16 +21,16 @@ public class SignalSystem implements Service { Map> typeServiceMap; /** - * The semaphore for thread-safing the system + * The lock for thread-safing the system */ - Semaphore systemLock; + ReentrantLock systemLock; /** * Initializes the signal system */ public void init(){ - typeServiceMap = new ConcurrentHashMap>(); - systemLock = new Semaphore(1); + typeServiceMap = new HashMap>(); + systemLock = new ReentrantLock(); } /** @@ -52,7 +52,7 @@ public class SignalSystem implements Service { * @param service The service to associate with that signal type */ public void registerService(SignalService service){ - systemLock.acquireUninterruptibly(); + systemLock.lock(); LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Register signal service " + service.getName()); for(SignalType signalType : service.getSubscriptionTargets()){ if(typeServiceMap.containsKey(signalType)){ @@ -66,7 +66,7 @@ public class SignalSystem implements Service { this.typeServiceMap.put(signalType, services); } } - systemLock.release(); + systemLock.unlock(); } /** @@ -75,7 +75,7 @@ public class SignalSystem implements Service { * @param service The service to associate with that signal type */ public void registerServiceToSignal(SignalType signalType, SignalService service){ - systemLock.acquireUninterruptibly(); + systemLock.lock(); LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Register signal service " + service.getName()); if(typeServiceMap.containsKey(signalType)){ Set services = this.typeServiceMap.get(signalType); @@ -87,7 +87,7 @@ public class SignalSystem implements Service { services.add(service); this.typeServiceMap.put(signalType, services); } - systemLock.release(); + systemLock.unlock(); } /** @@ -96,7 +96,7 @@ public class SignalSystem implements Service { * @param service The signal service to unassociate from that signal type */ public void deregisterServiceToSignal(SignalType signalType, SignalService service){ - systemLock.acquireUninterruptibly(); + systemLock.lock(); LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Deregister signal service " + service.getName()); if(typeServiceMap.containsKey(signalType)){ Set services = this.typeServiceMap.get(signalType); @@ -104,7 +104,7 @@ public class SignalSystem implements Service { } else { //there are no services mapped to this signal type } - systemLock.release(); + systemLock.unlock(); } /** @@ -113,7 +113,7 @@ public class SignalSystem implements Service { * @param data The data associated with the signal */ public void post(SignalType type, Object data){ - systemLock.acquireUninterruptibly(); + systemLock.lock(); if(typeServiceMap.containsKey(type)){ LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Post signal " + type); Signal signal = Signal.create(type, data); @@ -124,7 +124,7 @@ public class SignalSystem implements Service { } else { //there are no services mapped to this signal type } - systemLock.release(); + systemLock.unlock(); } /** diff --git a/src/main/java/electrosphere/net/client/MessageProtocol.java b/src/main/java/electrosphere/net/client/MessageProtocol.java index 83b06c3f..23e3cd4f 100644 --- a/src/main/java/electrosphere/net/client/MessageProtocol.java +++ b/src/main/java/electrosphere/net/client/MessageProtocol.java @@ -1,7 +1,6 @@ package electrosphere.net.client; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.LinkedList; import java.util.concurrent.Semaphore; import electrosphere.engine.Globals; @@ -41,7 +40,7 @@ public class MessageProtocol { Semaphore synchronousMessageLock = new Semaphore(1); //the queue of synchonous network messages - List synchronousMessageQueue = new CopyOnWriteArrayList(); + LinkedList synchronousMessageQueue = new LinkedList(); //The individual protocols diff --git a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java index 5b3d637c..fc98988f 100644 --- a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java +++ b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java @@ -1,13 +1,14 @@ -package electrosphere.net.parser.net.raw; - -import electrosphere.net.parser.net.message.MessagePool; -import electrosphere.net.parser.net.message.NetworkMessage; +package electrosphere.net.parser.net.raw; + +import electrosphere.net.parser.net.message.MessagePool; +import electrosphere.net.parser.net.message.NetworkMessage; import io.github.studiorailgun.CircularByteBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.LinkedList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; /** * The main message parser. This is used to serialize/deserialize messages to/from the provided streams. @@ -37,12 +38,12 @@ public class NetworkParser { /** * The queue of incoming messages that have been parsed */ - CopyOnWriteArrayList incomingMessageQueue = new CopyOnWriteArrayList(); + LinkedList incomingMessageQueue = new LinkedList(); /** * The queue of outgoing messages that have yet to be sent */ - CopyOnWriteArrayList outgoingMessageQueue = new CopyOnWriteArrayList(); + LinkedList outgoingMessageQueue = new LinkedList(); /** * Message object pool @@ -59,11 +60,6 @@ public class NetworkParser { */ byte[] readBuffer = new byte[READ_BLOCK_SIZE]; - /** - * The outgoing byte buffer - */ - CopyOnWriteArrayList outgoingByteQueue = new CopyOnWriteArrayList(); - /** * The number of bytes read */ @@ -74,6 +70,11 @@ public class NetworkParser { * Otherwise, will not release when the message is sent. */ boolean releaseOnSend = true; + + /** + * Lock for thread-safing the parser + */ + ReentrantLock lock = new ReentrantLock(); /** @@ -103,9 +104,11 @@ public class NetworkParser { //parse byte queue for messages //for each message, append to clientIncomingMessageQueue NetworkMessage newMessage; + lock.lock(); while((newMessage = NetworkMessage.parseBytestreamForMessage(incomingByteBuffer,this.pool))!=null){ incomingMessageQueue.add(newMessage); } + lock.unlock(); } /** @@ -113,13 +116,15 @@ public class NetworkParser { * @throws IOException Thrown if a message fails to serialize or the output stream fails to write */ public void pushMessagesOut() throws IOException { + lock.lock(); for(NetworkMessage message : outgoingMessageQueue){ - outgoingMessageQueue.remove(message); outgoingStream.write(message.getRawBytes()); if(this.releaseOnSend){ this.pool.release(message); } } + outgoingMessageQueue.clear(); + lock.unlock(); } /** @@ -127,7 +132,10 @@ public class NetworkParser { * @return true if there is message in the queue, false otherwise */ public boolean hasIncomingMessaage(){ - return incomingMessageQueue.size() > 0; + lock.lock(); + boolean rVal = incomingMessageQueue.size() > 0; + lock.unlock(); + return rVal; } /** @@ -135,7 +143,10 @@ public class NetworkParser { * @return The message */ public NetworkMessage popIncomingMessage(){ - return incomingMessageQueue.remove(0); + lock.lock(); + NetworkMessage rVal = incomingMessageQueue.remove(0); + lock.unlock(); + return rVal; } /** @@ -143,7 +154,9 @@ public class NetworkParser { * @param message The message */ public void addOutgoingMessage(NetworkMessage message){ + lock.lock(); outgoingMessageQueue.add(message); + lock.unlock(); } /** @@ -151,7 +164,9 @@ public class NetworkParser { * @param messages The list to copy the incoming messages to */ public void copyIncomingMessages(List messages){ + lock.lock(); messages.addAll(incomingMessageQueue); + lock.unlock(); } /** @@ -159,7 +174,9 @@ public class NetworkParser { * @param messages The list to copy the outgoing messages to */ public void copyOutgoingMessages(List messages){ + lock.lock(); messages.addAll(outgoingMessageQueue); + lock.unlock(); } /** diff --git a/src/main/java/electrosphere/net/server/MessageProtocol.java b/src/main/java/electrosphere/net/server/MessageProtocol.java index 2cef56d5..4161ca34 100644 --- a/src/main/java/electrosphere/net/server/MessageProtocol.java +++ b/src/main/java/electrosphere/net/server/MessageProtocol.java @@ -1,7 +1,6 @@ package electrosphere.net.server; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.LinkedList; import java.util.concurrent.Semaphore; import electrosphere.engine.Globals; @@ -37,7 +36,7 @@ public class MessageProtocol { Semaphore synchronousMessageLock = new Semaphore(1); //the queue of synchonous network messages - List synchronousMessageQueue = new CopyOnWriteArrayList(); + LinkedList synchronousMessageQueue = new LinkedList(); //The server connection handler ServerConnectionHandler serverConnectionHandler; diff --git a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java index e7696c0a..ce7aa651 100644 --- a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java +++ b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java @@ -16,7 +16,6 @@ import java.net.Socket; import java.net.SocketException; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -127,11 +126,6 @@ public class ServerConnectionHandler implements Runnable { * the lock used for synchronizing the synchronous message queue */ Semaphore synchronousMessageLock = new Semaphore(1); - - /** - * the queue of synchonous network messages - */ - List synchronousMessageQueue = new CopyOnWriteArrayList(); /** * Constructs a connection from a socket diff --git a/src/main/java/electrosphere/renderer/ui/ElementService.java b/src/main/java/electrosphere/renderer/ui/ElementService.java index 9b2d93e6..19c0ad0a 100644 --- a/src/main/java/electrosphere/renderer/ui/ElementService.java +++ b/src/main/java/electrosphere/renderer/ui/ElementService.java @@ -1,13 +1,13 @@ package electrosphere.renderer.ui; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.Stack; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; import org.joml.Vector2i; @@ -51,13 +51,18 @@ public class ElementService extends SignalServiceImpl { ); } - Map elementMap = new ConcurrentHashMap(); - List elementList = new CopyOnWriteArrayList(); + Map elementMap = new HashMap(); + List elementList = new LinkedList(); FocusableElement currentFocusedElement = null; DraggableElement currentDragElement = null; // the element currently hovered over HoverableElement currentHoveredElement = null; + + /** + * Lock for thread-safing the structure + */ + ReentrantLock lock = new ReentrantLock(); /** * Registers a window @@ -65,6 +70,7 @@ public class ElementService extends SignalServiceImpl { * @param w The window element */ public void registerWindow(String name, Element w){ + lock.lock(); elementMap.put(name,w); if(!elementList.contains(w)){ elementList.add(w); @@ -72,6 +78,7 @@ public class ElementService extends SignalServiceImpl { if(elementList.size() < 2){ focusFirstElement(); } + lock.unlock(); } /** @@ -80,7 +87,11 @@ public class ElementService extends SignalServiceImpl { * @return The window element if it exists, null otherwise */ public Element getWindow(String name){ - return elementMap.get(name); + Element rVal = null; + lock.lock(); + rVal = elementMap.get(name); + lock.unlock(); + return rVal; } /** @@ -88,7 +99,10 @@ public class ElementService extends SignalServiceImpl { * @return The list of all registered windows */ public List getWindowList(){ - return elementList; + lock.lock(); + List rVal = new LinkedList(this.elementList); + lock.unlock(); + return rVal; } /** @@ -96,6 +110,7 @@ public class ElementService extends SignalServiceImpl { * @param name The window string */ public void unregisterWindow(String name){ + lock.lock(); Element w = elementMap.remove(name); while(elementList.contains(w)){ elementList.remove(w); @@ -105,15 +120,21 @@ public class ElementService extends SignalServiceImpl { } else { this.currentFocusedElement = null; } + lock.unlock(); } public boolean containsWindow(String name){ - return elementMap.containsKey(name); + lock.lock(); + boolean rVal = elementMap.containsKey(name); + lock.unlock(); + return rVal; } public void pushWindowToFront(Window window){ + lock.lock(); elementList.remove(window); elementList.add(window); + lock.unlock(); } /** @@ -121,7 +142,10 @@ public class ElementService extends SignalServiceImpl { * @return The set of ids */ public Set getCurrentWindowIds(){ - return elementMap.keySet(); + lock.lock(); + Set rVal = elementMap.keySet(); + lock.unlock(); + return rVal; } /** @@ -159,6 +183,7 @@ public class ElementService extends SignalServiceImpl { } public void focusFirstElement(){ + lock.lock(); if(elementList.size() > 0){ List focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); if(focusables.size() > 0){ @@ -174,10 +199,13 @@ public class ElementService extends SignalServiceImpl { currentFocusedElement = null; } } + lock.unlock(); } public void focusNextElement(){ + lock.lock(); List focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); + lock.unlock(); if(focusables.contains(currentFocusedElement)){ int index = focusables.indexOf(currentFocusedElement); if(index + 1 >= focusables.size()){ @@ -194,7 +222,9 @@ public class ElementService extends SignalServiceImpl { } public void focusPreviousElement(){ + lock.lock(); List focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); + lock.unlock(); if(focusables.contains(currentFocusedElement)){ int index = focusables.indexOf(currentFocusedElement); if(index - 1 < 0){ @@ -230,7 +260,9 @@ public class ElementService extends SignalServiceImpl { */ public void fireEvent(Event event, int x, int y){ boolean propagate = true; + lock.lock(); ListIterator windowIterator = elementList.listIterator(elementList.size()); + lock.unlock(); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); Stack elementStack = buildElementPositionalStack(new Stack(), currentWindow, x, y); @@ -283,12 +315,14 @@ public class ElementService extends SignalServiceImpl { */ public void fireTopLevelEvent(Event event){ boolean propagate = true; + lock.lock(); for(Element topLevelEl : this.elementList){ propagate = fireEventNoPosition(event, topLevelEl); if(!propagate){ break; } } + lock.unlock(); } /** @@ -358,6 +392,7 @@ public class ElementService extends SignalServiceImpl { } public Element resolveFirstDraggable(DragEvent event){ + lock.lock(); ListIterator windowIterator = elementList.listIterator(elementList.size()); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); @@ -366,10 +401,12 @@ public class ElementService extends SignalServiceImpl { while(elementStack.size() > 0){ currentElement = elementStack.pop(); if(currentElement instanceof DraggableElement){ + lock.unlock(); return currentElement; } } } + lock.unlock(); return null; } @@ -380,6 +417,7 @@ public class ElementService extends SignalServiceImpl { * @return The first hoverable element if it exists, null otherwise */ public Element resolveFirstHoverable(int currentX, int currentY){ + lock.lock(); ListIterator windowIterator = elementList.listIterator(elementList.size()); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); @@ -388,10 +426,12 @@ public class ElementService extends SignalServiceImpl { while(elementStack.size() > 0){ currentElement = elementStack.pop(); if(currentElement instanceof HoverableElement){ + lock.unlock(); return currentElement; } } } + lock.unlock(); return null; } @@ -497,10 +537,13 @@ public class ElementService extends SignalServiceImpl { */ public void navigateBackwards(){ NavigationEvent event = new NavigationEvent(NavigationEventType.BACKWARD); + lock.lock(); + int elListSize = this.elementList.size(); + lock.unlock(); if(currentFocusedElement != null){ //fires on the currently focused element fireEventNoPosition(event, currentFocusedElement); - } else if(this.elementList.size() > 0){ + } else if(elListSize > 0){ fireTopLevelEvent(event); } } diff --git a/src/main/java/electrosphere/server/physics/terrain/generation/OverworldChunkGenerator.java b/src/main/java/electrosphere/server/physics/terrain/generation/OverworldChunkGenerator.java index 3b83f602..a5840f53 100644 --- a/src/main/java/electrosphere/server/physics/terrain/generation/OverworldChunkGenerator.java +++ b/src/main/java/electrosphere/server/physics/terrain/generation/OverworldChunkGenerator.java @@ -1,8 +1,5 @@ package electrosphere.server.physics.terrain.generation; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import electrosphere.client.terrain.cache.ChunkData; import electrosphere.server.physics.terrain.generation.interfaces.ChunkGenerator; import electrosphere.server.physics.terrain.manager.ServerTerrainChunk; @@ -19,7 +16,7 @@ public class OverworldChunkGenerator implements ChunkGenerator { //cache for the bicubic interpolated chunks //don't need to interpolate each time a new chunk is created //This should eventually be removed as terrain generation becomes more complicated than a heightmap - Map heightmapCache = new ConcurrentHashMap(); + // Map heightmapCache = new ConcurrentHashMap(); /** * Constructor