From 835fbd7957ff8287923efcc21531072b274cdcf0 Mon Sep 17 00:00:00 2001 From: Julien Seinturier Date: Fri, 8 Feb 2019 15:44:33 +0100 Subject: [PATCH] Jme3 vr (#828) * Updated OpenVR implementation to 1.0.9 User can specify external OpenVR library to load with openvr.library.path system property. Usage: java -Dopenvr.library.name=my_path_to_library MyApp Removed reference to OCCULUS VR and OpenVR from VRAppstate as this class is generic and does not have to be linked to specific implementation. VRMouseManager can be buggous using OSVR or Occulus VR. Refactored VR implementation packages in order to separate all available implementation. Modifying or adding implementation should no more impact other ones. Renamed some classes in order to be uniform * Sample and VR mouse manager update Adding sample for VR AppState Added OSVRMouseManager in order to handle VRMouseManager for OSVR environment Added OcculusMouseManager in order to handle VRMouseManager for Oculus environment Changed OpenVRMouseManager reference within VRAppState and VREnvironment into VRMouseManager * Revert previous commit as jme3-examples does not handle java8 and lwjgl3 * Adding AWT component rendering within app state Adding AWT component rendering capabilities for any JMonkeyEngine renderer (works with LWJGL 3.1.x and compatible with other implementations) This implementation differs from the previous one as it's relies on AppState (compatible with all JME application) and as it use only produces framebuffer. With this implementation, it is possible to use AWT component ad rendering target and so to integrate JMonkey rendering within AWT / Swing application. This capability was not available with LWJGL3 as the version 3.1.x does not provide anymore AWT link. * AWTComponentAppState update Added Update within AWTComponentAppState and added getter and setter for transfer mode within AWTFramePRocessor * Removing Java 8 related stuff --- .../jme3/app/state/AWTComponentAppState.java | 135 ++++ .../main/java/com/jme3/input/AWTInput.java | 131 ++++ .../main/java/com/jme3/input/AWTKeyInput.java | 237 +++++++ .../java/com/jme3/input/AWTMouseInput.java | 239 +++++++ .../com/jme3/system/AWTComponentRenderer.java | 391 +++++++++++ .../main/java/com/jme3/system/AWTContext.java | 234 +++++++ .../com/jme3/system/AWTFrameProcessor.java | 659 ++++++++++++++++++ .../java/com/jme3/system/AWTTaskExecutor.java | 114 +++ 8 files changed, 2140 insertions(+) create mode 100644 jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java create mode 100644 jme3-desktop/src/main/java/com/jme3/input/AWTInput.java create mode 100644 jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java create mode 100644 jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java create mode 100644 jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java create mode 100644 jme3-desktop/src/main/java/com/jme3/system/AWTContext.java create mode 100644 jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java create mode 100644 jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java new file mode 100644 index 000000000..0227aff0b --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import java.awt.Component; + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.system.AWTFrameProcessor; +import com.jme3.system.AWTTaskExecutor; + +/** + * An app state dedicated to the rendering of a JMonkey application within an AWT component. + * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + */ +public class AWTComponentAppState extends AbstractAppState { + + private final AWTTaskExecutor executor = AWTTaskExecutor.getInstance(); + + private Component component = null; + + private AWTFrameProcessor processor = null; + + private AWTFrameProcessor.TransferMode transferMode = AWTFrameProcessor.TransferMode.ON_CHANGES; + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + } + + @Override + public void stateAttached(final AppStateManager stateManager) { + processor = new AWTFrameProcessor(); + processor.setTransferMode(transferMode); + + AWTTaskExecutor.getInstance().addToExecute(new Runnable() { + + @Override + public void run() { + processor.bind(component, stateManager.getApplication(), stateManager.getApplication().getViewPort()); + } + + }); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + } + + @Override + public void update(float tpf) { + executor.execute(); + super.update(tpf); + } + + @Override + public void cleanup() { + super.cleanup(); + } + + /** + * Create a new app state dedicated to the rendering of a JMonkey application within the given AWT component. + * @param component the component that is used as rendering output. + */ + public AWTComponentAppState(Component component) { + this.component = component; + } + + /** + * Get the AWT component that is used as rendering output. + * @return the AWT component that is used as rendering output. + * @see #setComponent(Component) + */ + public Component getComponent() { + return component; + } + + /** + * Set the AWT component that is used as rendering output. + * @param component the AWT component that is used as rendering output. + * @see #getComponent() + */ + public void setComponent(Component component) { + this.component = component; + } + + /** + * Get the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @return the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @see #setTransferMode(com.jme3.system.AWTFrameProcessor.TransferMode) + */ + public AWTFrameProcessor.TransferMode getTransferMode(){ + return transferMode; + } + + /** + * Set the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @param mode the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor. + * @see #getTransferMode() + */ + public void setTransferMode(AWTFrameProcessor.TransferMode mode) { + this.transferMode = mode; + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java new file mode 100644 index 000000000..cceab1d6e --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.util.Objects; + +import com.jme3.app.Application; +import com.jme3.input.Input; +import com.jme3.input.RawInputListener; +import com.jme3.system.AWTContext; +import com.jme3.system.AWTTaskExecutor; + +/** + * The implementation of the {@link Input} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTInput implements Input { + + protected static final AWTTaskExecutor EXECUTOR = AWTTaskExecutor.getInstance(); + + /** + * The context. + */ + protected final AWTContext context; + + /** + * The raw listener. + */ + protected RawInputListener listener; + + /** + * The input node. + */ + protected Component component; + + /** + * The {@link Application JMonkey application} that provide the context. + */ + protected Application application; + + /** + * Initializes is it. + */ + protected boolean initialized; + + public AWTInput(final AWTContext context) { + this.context = context; + } + + public void bind(final Component component) { + this.component = component; + Objects.requireNonNull(this.component, "binded Component cannot be null"); + } + + public void unbind() { + this.component = null; + } + + @Override + public void initialize() { + if (isInitialized()) return; + initializeImpl(); + initialized = true; + } + + protected void initializeImpl() { + } + + @Override + public void update() { + if (!context.isRenderable()) return; + updateImpl(); + } + + protected void updateImpl() { + } + + @Override + public void destroy() { + unbind(); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java new file mode 100644 index 000000000..0280d8aed --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.system.AWTContext; + + +/** + * The implementation of the {@link KeyInput} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTKeyInput extends AWTInput implements KeyInput, KeyListener{ + + private static final Map KEY_CODE_TO_JME = new HashMap<>(); + + static { + KEY_CODE_TO_JME.put(KeyEvent.VK_ESCAPE, KEY_ESCAPE); + KEY_CODE_TO_JME.put(KeyEvent.VK_0, KEY_0); + KEY_CODE_TO_JME.put(KeyEvent.VK_1, KEY_1); + KEY_CODE_TO_JME.put(KeyEvent.VK_2, KEY_2); + KEY_CODE_TO_JME.put(KeyEvent.VK_3, KEY_3); + KEY_CODE_TO_JME.put(KeyEvent.VK_4, KEY_4); + KEY_CODE_TO_JME.put(KeyEvent.VK_5, KEY_5); + KEY_CODE_TO_JME.put(KeyEvent.VK_6, KEY_6); + KEY_CODE_TO_JME.put(KeyEvent.VK_7, KEY_7); + KEY_CODE_TO_JME.put(KeyEvent.VK_8, KEY_8); + KEY_CODE_TO_JME.put(KeyEvent.VK_9, KEY_9); + KEY_CODE_TO_JME.put(KeyEvent.VK_MINUS, KEY_MINUS); + KEY_CODE_TO_JME.put(KeyEvent.VK_EQUALS, KEY_EQUALS); + KEY_CODE_TO_JME.put(KeyEvent.VK_BACK_SPACE, KEY_BACK); + KEY_CODE_TO_JME.put(KeyEvent.VK_TAB, KEY_TAB); + KEY_CODE_TO_JME.put(KeyEvent.VK_Q, KEY_Q); + KEY_CODE_TO_JME.put(KeyEvent.VK_W, KEY_W); + KEY_CODE_TO_JME.put(KeyEvent.VK_E, KEY_E); + KEY_CODE_TO_JME.put(KeyEvent.VK_R, KEY_R); + KEY_CODE_TO_JME.put(KeyEvent.VK_T, KEY_T); + KEY_CODE_TO_JME.put(KeyEvent.VK_U, KEY_U); + KEY_CODE_TO_JME.put(KeyEvent.VK_I, KEY_I); + KEY_CODE_TO_JME.put(KeyEvent.VK_O, KEY_O); + KEY_CODE_TO_JME.put(KeyEvent.VK_P, KEY_P); + KEY_CODE_TO_JME.put(KeyEvent.VK_OPEN_BRACKET, KEY_LBRACKET); + KEY_CODE_TO_JME.put(KeyEvent.VK_CLOSE_BRACKET, KEY_RBRACKET); + KEY_CODE_TO_JME.put(KeyEvent.VK_ENTER, KEY_RETURN); + KEY_CODE_TO_JME.put(KeyEvent.VK_CONTROL, KEY_LCONTROL); + KEY_CODE_TO_JME.put(KeyEvent.VK_A, KEY_A); + KEY_CODE_TO_JME.put(KeyEvent.VK_S, KEY_S); + KEY_CODE_TO_JME.put(KeyEvent.VK_D, KEY_D); + KEY_CODE_TO_JME.put(KeyEvent.VK_F, KEY_F); + KEY_CODE_TO_JME.put(KeyEvent.VK_G, KEY_G); + KEY_CODE_TO_JME.put(KeyEvent.VK_H, KEY_H); + KEY_CODE_TO_JME.put(KeyEvent.VK_J, KEY_J); + KEY_CODE_TO_JME.put(KeyEvent.VK_Y, KEY_Y); + KEY_CODE_TO_JME.put(KeyEvent.VK_K, KEY_K); + KEY_CODE_TO_JME.put(KeyEvent.VK_L, KEY_L); + KEY_CODE_TO_JME.put(KeyEvent.VK_SEMICOLON, KEY_SEMICOLON); + KEY_CODE_TO_JME.put(KeyEvent.VK_QUOTE, KEY_APOSTROPHE); + KEY_CODE_TO_JME.put(KeyEvent.VK_DEAD_GRAVE, KEY_GRAVE); + KEY_CODE_TO_JME.put(KeyEvent.VK_SHIFT, KEY_LSHIFT); + KEY_CODE_TO_JME.put(KeyEvent.VK_BACK_SLASH, KEY_BACKSLASH); + KEY_CODE_TO_JME.put(KeyEvent.VK_Z, KEY_Z); + KEY_CODE_TO_JME.put(KeyEvent.VK_X, KEY_X); + KEY_CODE_TO_JME.put(KeyEvent.VK_C, KEY_C); + KEY_CODE_TO_JME.put(KeyEvent.VK_V, KEY_V); + KEY_CODE_TO_JME.put(KeyEvent.VK_B, KEY_B); + KEY_CODE_TO_JME.put(KeyEvent.VK_N, KEY_N); + KEY_CODE_TO_JME.put(KeyEvent.VK_M, KEY_M); + KEY_CODE_TO_JME.put(KeyEvent.VK_COMMA, KEY_COMMA); + KEY_CODE_TO_JME.put(KeyEvent.VK_PERIOD, KEY_PERIOD); + KEY_CODE_TO_JME.put(KeyEvent.VK_SLASH, KEY_SLASH); + KEY_CODE_TO_JME.put(KeyEvent.VK_MULTIPLY, KEY_MULTIPLY); + KEY_CODE_TO_JME.put(KeyEvent.VK_SPACE, KEY_SPACE); + KEY_CODE_TO_JME.put(KeyEvent.VK_CAPS_LOCK, KEY_CAPITAL); + KEY_CODE_TO_JME.put(KeyEvent.VK_F1, KEY_F1); + KEY_CODE_TO_JME.put(KeyEvent.VK_F2, KEY_F2); + KEY_CODE_TO_JME.put(KeyEvent.VK_F3, KEY_F3); + KEY_CODE_TO_JME.put(KeyEvent.VK_F4, KEY_F4); + KEY_CODE_TO_JME.put(KeyEvent.VK_F5, KEY_F5); + KEY_CODE_TO_JME.put(KeyEvent.VK_F6, KEY_F6); + KEY_CODE_TO_JME.put(KeyEvent.VK_F7, KEY_F7); + KEY_CODE_TO_JME.put(KeyEvent.VK_F8, KEY_F8); + KEY_CODE_TO_JME.put(KeyEvent.VK_F9, KEY_F9); + KEY_CODE_TO_JME.put(KeyEvent.VK_F10, KEY_F10); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUM_LOCK, KEY_NUMLOCK); + KEY_CODE_TO_JME.put(KeyEvent.VK_SCROLL_LOCK, KEY_SCROLL); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD7, KEY_NUMPAD7); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD8, KEY_NUMPAD8); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD9, KEY_NUMPAD9); + KEY_CODE_TO_JME.put(KeyEvent.VK_SUBTRACT, KEY_SUBTRACT); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD4, KEY_NUMPAD4); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD5, KEY_NUMPAD5); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD6, KEY_NUMPAD6); + KEY_CODE_TO_JME.put(KeyEvent.VK_ADD, KEY_ADD); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD1, KEY_NUMPAD1); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD2, KEY_NUMPAD2); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD3, KEY_NUMPAD3); + KEY_CODE_TO_JME.put(KeyEvent.VK_NUMPAD0, KEY_NUMPAD0); + KEY_CODE_TO_JME.put(KeyEvent.VK_DECIMAL, KEY_DECIMAL); + KEY_CODE_TO_JME.put(KeyEvent.VK_F11, KEY_F11); + KEY_CODE_TO_JME.put(KeyEvent.VK_F12, KEY_F12); + KEY_CODE_TO_JME.put(KeyEvent.VK_F13, KEY_F13); + KEY_CODE_TO_JME.put(KeyEvent.VK_F14, KEY_F14); + KEY_CODE_TO_JME.put(KeyEvent.VK_F15, KEY_F15); + KEY_CODE_TO_JME.put(KeyEvent.VK_KANA, KEY_KANA); + KEY_CODE_TO_JME.put(KeyEvent.VK_CONVERT, KEY_CONVERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_NONCONVERT, KEY_NOCONVERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_CIRCUMFLEX, KEY_CIRCUMFLEX); + KEY_CODE_TO_JME.put(KeyEvent.VK_AT, KEY_AT); + KEY_CODE_TO_JME.put(KeyEvent.VK_COLON, KEY_COLON); + KEY_CODE_TO_JME.put(KeyEvent.VK_UNDERSCORE, KEY_UNDERLINE); + KEY_CODE_TO_JME.put(KeyEvent.VK_STOP, KEY_STOP); + KEY_CODE_TO_JME.put(KeyEvent.VK_DIVIDE, KEY_DIVIDE); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAUSE, KEY_PAUSE); + KEY_CODE_TO_JME.put(KeyEvent.VK_HOME, KEY_HOME); + KEY_CODE_TO_JME.put(KeyEvent.VK_UP, KEY_UP); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAGE_UP, KEY_PRIOR); + KEY_CODE_TO_JME.put(KeyEvent.VK_LEFT, KEY_LEFT); + KEY_CODE_TO_JME.put(KeyEvent.VK_RIGHT, KEY_RIGHT); + KEY_CODE_TO_JME.put(KeyEvent.VK_END, KEY_END); + KEY_CODE_TO_JME.put(KeyEvent.VK_DOWN, KEY_DOWN); + KEY_CODE_TO_JME.put(KeyEvent.VK_PAGE_DOWN, KEY_NEXT); + KEY_CODE_TO_JME.put(KeyEvent.VK_INSERT, KEY_INSERT); + KEY_CODE_TO_JME.put(KeyEvent.VK_DELETE, KEY_DELETE); + KEY_CODE_TO_JME.put(KeyEvent.VK_ALT, KEY_LMENU); + KEY_CODE_TO_JME.put(KeyEvent.VK_META, KEY_RCONTROL); + } + + private final LinkedList keyInputEvents; + + public AWTKeyInput(AWTContext context) { + super(context); + keyInputEvents = new LinkedList(); + } + + @Override + public void bind(Component component) { + super.bind(component); + component.addKeyListener(this); + } + + @Override + public void unbind() { + if (component != null) { + component.removeKeyListener(this); + } + super.unbind(); + } + + private void onKeyEvent(KeyEvent keyEvent, boolean pressed) { + + int code = convertKeyCode(keyEvent.getID()); + char keyChar = keyEvent.getKeyChar(); + + final KeyInputEvent event = new KeyInputEvent(code, keyChar, pressed, false); + event.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + keyInputEvents.add(event); + } + + }); + } + + @Override + protected void updateImpl() { + while (!keyInputEvents.isEmpty()) { + listener.onKeyEvent(keyInputEvents.poll()); + } + } + + private int convertKeyCode(int keyCode) { + final Integer code = KEY_CODE_TO_JME.get(keyCode); + return code == null ? KEY_UNKNOWN : code; + } + + @Override + public void keyTyped(KeyEvent e) { + System.out.println("Key typed "+e.getKeyChar()); + //onKeyEvent(e, false); + } + + @Override + public void keyPressed(KeyEvent e) { + System.out.println("Key pressed "+e.getKeyChar()); + onKeyEvent(e, true); + } + + @Override + public void keyReleased(KeyEvent e) { + System.out.println("Key released "+e.getKeyChar()); + onKeyEvent(e, false); + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java new file mode 100644 index 000000000..72b75a5d3 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.system.AWTContext; + +/** + * The implementation of the {@link MouseInput} dedicated to AWT {@link Component component}. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTMouseInput extends AWTInput implements MouseInput, MouseListener, MouseMotionListener, MouseWheelListener { + + private static final Map MOUSE_BUTTON_TO_JME = new HashMap<>(); + + static { + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON1, BUTTON_LEFT); + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON2, BUTTON_RIGHT); + MOUSE_BUTTON_TO_JME.put(MouseEvent.BUTTON3, BUTTON_MIDDLE); + } + + /** + * The scale factor for scrolling. + */ + private static final int WHEEL_SCALE = 10; + + private final LinkedList mouseMotionEvents; + + private final LinkedList mouseButtonEvents; + + private int mouseX; + private int mouseY; + private int mouseWheel; + + public AWTMouseInput(AWTContext context) { + super(context); + mouseMotionEvents = new LinkedList(); + mouseButtonEvents = new LinkedList(); + } + + @Override + public void bind(Component component) { + super.bind(component); + component.addMouseListener(this); + component.addMouseMotionListener(this); + component.addMouseWheelListener(this); + } + + @Override + public void unbind() { + if (component != null) { + component.removeMouseListener(this); + component.removeMouseMotionListener(this); + component.removeMouseWheelListener(this); + } + super.unbind(); + } + + @Override + protected void updateImpl() { + while (!mouseMotionEvents.isEmpty()) { + listener.onMouseMotionEvent(mouseMotionEvents.poll()); + } + while (!mouseButtonEvents.isEmpty()) { + listener.onMouseButtonEvent(mouseButtonEvents.poll()); + } + } + + private void onWheelScroll(final double xOffset, final double yOffset) { + + mouseWheel += yOffset; + + final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset)); + mouseMotionEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseMotionEvents.add(mouseMotionEvent); + } + + }); + } + + private void onCursorPos(double xpos, double ypos) { + + int xDelta; + int yDelta; + int x = (int) Math.round(xpos); + int y = context.getHeight() - (int) Math.round(ypos); + + if (mouseX == 0) mouseX = x; + if (mouseY == 0) mouseY = y; + + xDelta = x - mouseX; + yDelta = y - mouseY; + + mouseX = x; + mouseY = y; + + if (xDelta == 0 && yDelta == 0) return; + + final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y, xDelta, yDelta, mouseWheel, 0); + mouseMotionEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseMotionEvents.add(mouseMotionEvent); + } + + }); + } + + private void onMouseButton(MouseEvent event, final boolean pressed) { + + final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(convertButton(event.getButton()), pressed, mouseX, mouseY); + mouseButtonEvent.setTime(getInputTimeNanos()); + + EXECUTOR.addToExecute(new Runnable() { + + @Override + public void run() { + mouseButtonEvents.add(mouseButtonEvent); + } + + }); + } + + private int convertButton(int i) { + final Integer result = MOUSE_BUTTON_TO_JME.get(i); + return result == null ? 0 : result; + } + + @Override + public void setCursorVisible(final boolean visible) { + } + + @Override + public int getButtonCount() { + return 3; + } + + @Override + public void setNativeCursor(JmeCursor cursor) { + } + + @Override + public void mouseDragged(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseMoved(java.awt.event.MouseEvent e) { + onCursorPos(e.getX(), e.getY()); + } + + @Override + public void mouseClicked(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mousePressed(java.awt.event.MouseEvent e) { + onMouseButton(e, true); + } + + @Override + public void mouseReleased(java.awt.event.MouseEvent e) { + onMouseButton(e, false); + } + + @Override + public void mouseEntered(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseExited(java.awt.event.MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + onWheelScroll(e.getWheelRotation() * WHEEL_SCALE, e.getWheelRotation() * WHEEL_SCALE); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java new file mode 100644 index 000000000..99e745675 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.system.AWTFrameProcessor.TransferMode; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.util.BufferUtils; + + +/** + *

+ * This class enables to update graphics of an AWT component with the result of JMonkey 3D rendering. + *

+ *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTComponentRenderer { + + /** + * The constant RUNNING_STATE. + */ + protected static final int RUNNING_STATE = 1; + /** + * The constant WAITING_STATE. + */ + protected static final int WAITING_STATE = 2; + /** + * The constant DISPOSING_STATE. + */ + protected static final int DISPOSING_STATE = 3; + /** + * The constant DISPOSED_STATE. + */ + protected static final int DISPOSED_STATE = 4; + + /** + * The Frame state. + */ + protected final AtomicInteger frameState; + + /** + * The Image state. + */ + protected final AtomicInteger imageState; + + /** + * The Frame buffer. + */ + protected final FrameBuffer frameBuffer; + + /** + * The Pixel writer. + */ + protected final Graphics pixelWriter; + + /** + * The Frame byte buffer. + */ + protected final ByteBuffer frameByteBuffer; + + + /** + * The transfer mode. + */ + protected final TransferMode transferMode; + + /** + * The byte buffer. + */ + protected final byte[] byteBuffer; + + /** + * The image byte buffer. + */ + protected final int[] imageByteBuffer; + + /** + * The prev image byte buffer. + */ + protected final byte[] prevImageByteBuffer; + + /** + * How many frames need to write else. + */ + protected int frameCount; + + /** + * The width. + */ + private final int width; + + /** + * The height. + */ + private final int height; + + private ColorModel colorModel = null; + + private Component component = null; + + private Graphics2D offGraphics = null; + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param width the width of the component in pixels. + * @param height the height of the component in pixels. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update + * or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + */ + public AWTComponentRenderer(Component destination, final int width, final int height, TransferMode transferMode) { + this(destination, transferMode, null, width, height); + } + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + * @param frameBuffer the JMonkey frame buffer to use (if null is passed, a new default frame buffer is created) + * @param width the width of the component in pixels. + * @param height the height of the component in pixels. + */ + public AWTComponentRenderer(Component destination, TransferMode transferMode, FrameBuffer frameBuffer, int width, int height) { + this.transferMode = transferMode; + this.frameState = new AtomicInteger(WAITING_STATE); + this.imageState = new AtomicInteger(WAITING_STATE); + this.width = frameBuffer != null ? frameBuffer.getWidth() : width; + this.height = frameBuffer != null ? frameBuffer.getHeight() : height; + this.frameCount = 0; + + if (frameBuffer != null) { + this.frameBuffer = frameBuffer; + } else { + this.frameBuffer = new FrameBuffer(width, height, 1); + this.frameBuffer.setDepthBuffer(Image.Format.Depth); + this.frameBuffer.setColorBuffer(Image.Format.RGBA8); + this.frameBuffer.setSrgb(true); + } + + colorModel = ColorModel.getRGBdefault(); + + frameByteBuffer = BufferUtils.createByteBuffer(getWidth() * getHeight() * 4); + byteBuffer = new byte[getWidth() * getHeight() * 4]; + prevImageByteBuffer = new byte[getWidth() * getHeight() * 4]; + imageByteBuffer = new int[getWidth() * getHeight()]; + pixelWriter = getGraphics(destination); + + this.component = destination; + + } + + /** + * Initialize the component renderer. + * @param renderer the JMonkey {@link Renderer renderer} to use. + * @param main true if the attached frame buffer is the main one or false otherwise. + */ + public void init(Renderer renderer, boolean main) { + if (main) { + renderer.setMainFrameBufferOverride(frameBuffer); + } + } + + /** + * Get the graphics context of the given component. + * @param destination the AWT component used for rendering. + * @return the graphics context of the given component. + */ + protected Graphics getGraphics(Component destination) { + if (destination != null) { + + if (destination.getGraphics() != null) { + return destination.getGraphics(); + } else { + System.out.println("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities."); + return null; + //throw new IllegalArgumentException("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities."); + } + } else { + throw new NullPointerException("Component cannot be null."); + } + } + + /** + * Get the width of the area to render. + * @return the width of the area to render. + * @see #getHeight() + */ + public int getWidth() { + return width; + } + + /** + * Get the height of the area to render. + * @return the height of the area to render. + * @see #getWidth() + */ + public int getHeight() { + return height; + } + + /** + * Copy the JMonkey frame buffer that has been rendered by the JMonkey engine and schedule the rendering of the component. + * @param renderManager the JMonkey render manager. + */ + public void copyFrameBufferToImage(RenderManager renderManager) { + + while (!frameState.compareAndSet(WAITING_STATE, RUNNING_STATE)) { + if (frameState.get() == DISPOSED_STATE) { + return; + } + } + + // Convert screenshot. + try { + + frameByteBuffer.clear(); + + final Renderer renderer = renderManager.getRenderer(); + renderer.readFrameBufferWithFormat(frameBuffer, frameByteBuffer, Image.Format.RGBA8); + + } finally { + if (!frameState.compareAndSet(RUNNING_STATE, WAITING_STATE)) { + throw new RuntimeException("unknown problem with the frame state"); + } + } + + synchronized (byteBuffer) { + frameByteBuffer.get(byteBuffer); + + if (transferMode == TransferMode.ON_CHANGES) { + + final byte[] prevBuffer = getPrevImageByteBuffer(); + + if (Arrays.equals(prevBuffer, byteBuffer)) { + if (frameCount == 0) return; + } else { + frameCount = 2; + System.arraycopy(byteBuffer, 0, prevBuffer, 0, byteBuffer.length); + } + + frameByteBuffer.position(0); + frameCount--; + } + } + + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + writeFrame(); + }}); + } + + /** + * Write the current rendered frame to the component graphics contex. + */ + protected void writeFrame() { + + + if (pixelWriter != null) { + while (!imageState.compareAndSet(WAITING_STATE, RUNNING_STATE)) { + if (imageState.get() == DISPOSED_STATE) { + return; + } + } + + try { + + final int[] imageDataBuffer = getImageByteBuffer(); + + synchronized (byteBuffer) { + + for(int i = 0; i < width * height; i++) { + imageDataBuffer[i] = ((0xff & byteBuffer[i*4+3]) << 24) // Alpha + | ((0xff & byteBuffer[i*4]) << 16) // Red + | ((0xff & byteBuffer[i*4+1]) << 8) // Green + | ((0xff & byteBuffer[i*4+2])); // BLue + + } + } + + DataBuffer buffer = new DataBufferInt(imageDataBuffer, imageDataBuffer.length); + + SampleModel sm = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, getWidth(), getHeight(), new int[] {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}); + + WritableRaster raster = Raster.createWritableRaster(sm, buffer, null); + + BufferedImage img = new BufferedImage(colorModel, raster, false, null); + + BufferedImage img2 = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); + offGraphics = img2.createGraphics(); + offGraphics.setColor(component.getBackground()); + img2.createGraphics().fillRect(0, 0, getWidth(), getHeight()); + img2.createGraphics().drawImage(img, null, null); + + component.getGraphics().drawImage(img2, 0, 0, null); + + } finally { + if (!imageState.compareAndSet(RUNNING_STATE, WAITING_STATE)) { + throw new RuntimeException("unknown problem with the image state"); + } + } + } else { + System.out.println("No graphics context available for rendering."); + } + + + } + + /** + * Get the image byte buffer. + * @return the image byte buffer. + */ + protected int[] getImageByteBuffer() { + return imageByteBuffer; + } + + /** + * Get the previous image byte buffer. + * @return the previous image byte buffer. + */ + protected byte[] getPrevImageByteBuffer() { + return prevImageByteBuffer; + } + + /** + * Dispose this renderer. The underlying frame buffer is also disposed. + */ + public void dispose() { + while (!frameState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ; + while (!imageState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ; + frameBuffer.dispose(); + BufferUtils.destroyDirectBuffer(frameByteBuffer); + frameState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); + imageState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java new file mode 100644 index 000000000..78cde8f74 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + + +import com.jme3.input.AWTKeyInput; +import com.jme3.input.AWTMouseInput; +import com.jme3.input.JoyInput; +import com.jme3.input.TouchInput; +import com.jme3.opencl.Context; +import com.jme3.renderer.Renderer; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.JmeSystem; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; + +/** + * A JMonkey {@link JmeContext context} that is dedicated to AWT component rendering. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTContext implements JmeContext { + + /** + * The settings. + */ + protected final AppSettings settings; + + /** + * The key input. + */ + protected final AWTKeyInput keyInput; + + /** + * The mouse input. + */ + protected final AWTMouseInput mouseInput; + + /** + * The current width. + */ + private volatile int width; + + /** + * The current height. + */ + private volatile int height; + + /** + * The background context. + */ + protected JmeContext backgroundContext; + + public AWTContext() { + this.keyInput = new AWTKeyInput(this); + this.mouseInput = new AWTMouseInput(this); + this.settings = createSettings(); + this.backgroundContext = createBackgroundContext(); + this.height = 1; + this.width = 1; + } + + /** + * @return the current height. + */ + public int getHeight() { + return height; + } + + /** + * @param height the current height. + */ + public void setHeight(final int height) { + this.height = height; + } + + /** + * @return the current width. + */ + public int getWidth() { + return width; + } + + /** + * @param width the current width. + */ + public void setWidth(final int width) { + this.width = width; + } + + /** + * @return new settings. + */ + protected AppSettings createSettings() { + final AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL3); + return settings; + } + + /** + * @return new context/ + */ + protected JmeContext createBackgroundContext() { + return JmeSystem.newContext(settings, Type.OffscreenSurface); + } + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL3); + this.backgroundContext.setSettings(settings); + } + + @Override + public void setSystemListener(final SystemListener listener) { + backgroundContext.setSystemListener(listener); + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public Renderer getRenderer() { + return backgroundContext.getRenderer(); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public AWTMouseInput getMouseInput() { + return mouseInput; + } + + @Override + public AWTKeyInput getKeyInput() { + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public Timer getTimer() { + return backgroundContext.getTimer(); + } + + @Override + public void setTitle(final String title) { + } + + @Override + public boolean isCreated() { + return backgroundContext != null && backgroundContext.isCreated(); + } + + @Override + public boolean isRenderable() { + return backgroundContext != null && backgroundContext.isRenderable(); + } + + @Override + public void setAutoFlushFrames(final boolean enabled) { + // TODO Auto-generated method stub + } + + @Override + public void create(final boolean waitFor) { + String render = System.getProperty("awt.background.render", AppSettings.LWJGL_OPENGL33); + backgroundContext.getSettings().setRenderer(render); + backgroundContext.create(waitFor); + } + + @Override + public void restart() { + } + + @Override + public void destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + backgroundContext.destroy(waitFor); +} + +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java new file mode 100644 index 000000000..81dc7b029 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Component; +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import com.jme3.app.Application; +import com.jme3.input.AWTKeyInput; +import com.jme3.input.AWTMouseInput; +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; + +/** + * A frame processor that enables to render JMonkey frame buffer onto an AWT component. + *

+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTFrameProcessor implements SceneProcessor, PropertyChangeListener { + + public enum TransferMode { + ALWAYS, + ON_CHANGES + } + + private Application application = null; + + /** + * The width listener. + */ + protected PropertyChangeListener widthListener; + + /** + * The height listener. + */ + protected PropertyChangeListener heightListener; + + /** + * The ration listener. + */ + protected PropertyChangeListener rationListener; + + /** + * The flag to decide when we should resize. + */ + private final AtomicInteger reshapeNeeded; + + /** + * The render manager. + */ + private RenderManager renderManager; + + /** + * The source view port. + */ + private ViewPort viewPort; + + /** + * The frame transfer. + */ + private AWTComponentRenderer frameTransfer; + + /** + * The transfer mode. + */ + private TransferMode transferMode; + + /** + * The destination of jMe frames. + */ + protected volatile Component destination; + + /** + * The flag is true if this processor is main. + */ + private volatile boolean main; + + private int askWidth; + private int askHeight; + + private boolean askFixAspect; + private boolean enabled; + + @Override + public void initialize(RenderManager rm, ViewPort vp) { + this.renderManager = rm; + } + + @Override + public void reshape(ViewPort vp, int w, int h) { + // TODO Auto-generated method stub + } + + @Override + public boolean isInitialized() { + return frameTransfer != null; + } + + @Override + public void preFrame(float tpf) { + // TODO Auto-generated method stub + + } + + @Override + public void postQueue(RenderQueue rq) { + // TODO Auto-generated method stub + + } + + @Override + public void postFrame(FrameBuffer out) { + if (!isEnabled()) { + return; + } + + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + if (frameTransfer != null) { + frameTransfer.copyFrameBufferToImage(getRenderManager()); + } + + // for the next frame + if (hasDestination() && reshapeNeeded.get() > 0 && reshapeNeeded.decrementAndGet() >= 0) { + + if (frameTransfer != null) { + frameTransfer.dispose(); + } + + setFrameTransfer(reshapeInThread(askWidth, askHeight, askFixAspect)); + } + } + + @Override + public void cleanup() { + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + + if (frameTransfer != null) { + frameTransfer.dispose(); + setFrameTransfer(null); + } + } + + @Override + public void setProfiler(AppProfiler profiler) { + // TODO Auto-generated method stub + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + System.out.println("Property changed: "+evt.getPropertyName()+" "+evt.getOldValue()+" -> "+evt.getNewValue()); + } + + public AWTFrameProcessor() { + transferMode = TransferMode.ALWAYS; + askWidth = 1; + askHeight = 1; + main = true; + reshapeNeeded = new AtomicInteger(2); + } + + /** + * Notify about that the ratio was changed. + * + * @param newValue the new value of the ratio. + */ + protected void notifyChangedRatio(Boolean newValue) { + notifyComponentResized(destination.getWidth(), destination.getHeight(), newValue); + } + + /** + * Notify about that the height was changed. + * + * @param newValue the new value of the height. + */ + protected void notifyChangedHeight(Number newValue) { + notifyComponentResized(destination.getWidth(), newValue.intValue(), isPreserveRatio()); + } + + /** + * Notify about that the width was changed. + * + * @param newValue the new value of the width. + */ + protected void notifyChangedWidth(Number newValue) { + notifyComponentResized(newValue.intValue(), destination.getHeight(), isPreserveRatio()); + } + + /** + * Gets the application. + * + * @return the application. + */ + protected Application getApplication() { + return application; + } + + /** + * Sets the application. + * + * @param application the application. + */ + protected void setApplication(Application application) { + this.application = application; + } + + /** + * Gets the current destination. + * + * @return the current destination. + */ + protected Component getDestination() { + return destination; + } + + /** + * Sets the destination. + * + * @param destination the destination. + */ + protected void setDestination(Component destination) { + this.destination = destination; + } + + /** + * Checks of existing destination. + * @return true if destination is exists. + */ + protected boolean hasDestination() { + return destination != null; + } + + /** + * Checks of existing application. + * @return true if destination is exists. + */ + protected boolean hasApplication() { + return application != null; + } + + + /** + * Gets the frame transfer. + * @return the file transfer. + */ + protected AWTComponentRenderer getFrameTransfer() { + return frameTransfer; + } + + /** + * Sets the frame transfer. + * + * @param frameTransfer the file transfer. + */ + protected void setFrameTransfer(AWTComponentRenderer frameTransfer) { + this.frameTransfer = frameTransfer; + } + + /** + * Gets the view port. + * + * @return the view port. + */ + protected ViewPort getViewPort() { + return viewPort; + } + + /** + * Gets the render manager. + * + * @return the render manager. + */ + protected RenderManager getRenderManager() { + return renderManager; + } + + public boolean isMain() { + return main; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Handle resizing. + * + * @param newWidth the new width. + * @param newHeight the new height. + * @param fixAspect true if need to fix aspect. + */ + protected void notifyComponentResized(int newWidth, int newHeight, boolean fixAspect) { + + newWidth = Math.max(newWidth, 1); + newHeight = Math.max(newHeight, 1); + + if (askWidth == newWidth && askWidth == newHeight && askFixAspect == fixAspect) { + return; + } + + askWidth = newWidth; + askHeight = newHeight; + askFixAspect = fixAspect; + reshapeNeeded.set(2); + } + + public void reshape() { + reshapeNeeded.set(2); + } + + /** + * Is preserve ratio. + * + * @return is preserve ratio. + */ + protected boolean isPreserveRatio() { + return false; + } + + /** + * Gets destination width. + * + * @return the destination width. + */ + protected int getDestinationWidth() { + return getDestination().getWidth(); + } + + /** + * Gets destination height. + * + * @return the destination height. + */ + protected int getDestinationHeight() { + return getDestination().getHeight(); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + */ + public void bind(Component destination, Application application) { + final RenderManager renderManager = application.getRenderManager(); + + if (renderManager == null) { + throw new RuntimeException("No render manager available from the application."); + } + + List postViews = renderManager.getPostViews(); + if (postViews.isEmpty()) { + throw new RuntimeException("the list of a post view is empty."); + } + + bind(destination, application, postViews.get(postViews.size() - 1), true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param viewPort the view port. + */ + public void bind(Component destination, Application application, ViewPort viewPort) { + bind(destination, application, viewPort, true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param inputNode the input node. + * @param viewPort the view port. + * @param main true if this processor is main. + */ + public void bind(final Component destination, final Application application, ViewPort viewPort, boolean main) { + + if (hasApplication()) { + throw new RuntimeException("This process is already bonded."); + } + + setApplication(application); + setEnabled(true); + + this.main = main; + this.viewPort = viewPort; + this.viewPort.addProcessor(this); + + if (EventQueue.isDispatchThread()) { + bindDestination(application, destination); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + bindDestination(application, destination); + }}); + } + } + + /** + * Unbind this processor from its current destination. + */ + public void unbind() { + + if (viewPort != null) { + viewPort.removeProcessor(this); + viewPort = null; + } + + if (EventQueue.isDispatchThread()) { + unbindDestination(); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + unbindDestination(); + }}); + } + + } + + /** + * Bind this processor. + * + * @param application the application. + * @param destination the destination. + * @param inputNode the input node. + */ + protected void bindDestination(Application application, Component destination) { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("bind has to be done from the Event Dispatching thread."); + } + + if (isMain()) { + + if (application.getContext() != null) { + if (application.getContext() instanceof AWTContext) { + AWTContext context = (AWTContext) application.getContext(); + AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.bind(destination); + AWTKeyInput keyInput = context.getKeyInput(); + keyInput.bind(destination); + + setDestination(destination); + bindListeners(); + + notifyComponentResized(getDestinationWidth(), getDestinationHeight(), isPreserveRatio()); + + } else { + throw new IllegalArgumentException("Underlying application has to use AWTContext (actually using "+application.getContext().getClass().getSimpleName()+")"); + } + } else { + throw new IllegalArgumentException("Underlying application has to use a valid AWTContext (context is null)"); + } + } + } + + /** + * Unbind this processor from destination. + */ + protected void unbindDestination() { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("unbind has to be done from the Event Dispatching thread."); + } + + if (hasApplication() && isMain()) { + final AWTContext context = (AWTContext) getApplication().getContext(); + final AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.unbind(); + final AWTKeyInput keyInput = context.getKeyInput(); + keyInput.unbind(); + } + + setApplication(null); + + if (hasDestination()) { + unbindListeners(); + setDestination(null); + } + } + + + protected void bindListeners() { + Component destination = getDestination(); + destination.addPropertyChangeListener(this); + destination.addPropertyChangeListener(this); + } + + + protected void unbindListeners() { + Component destination = getDestination(); + destination.removePropertyChangeListener(this); + destination.removePropertyChangeListener(this); + } + + /** + * Reshape the current frame transfer for the new size. + * + * @param width the width. + * @param height the height. + * @param fixAspect true if need to fix aspect ration. + * @return the new frame transfer. + */ + protected AWTComponentRenderer reshapeInThread(final int width, final int height, final boolean fixAspect) { + + reshapeCurrentViewPort(width, height); + + ViewPort viewPort = getViewPort(); + RenderManager renderManager = getRenderManager(); + FrameBuffer frameBuffer = viewPort.getOutputFrameBuffer(); + + AWTComponentRenderer frameTransfer = createFrameTransfer(frameBuffer, width, height); + frameTransfer.init(renderManager.getRenderer(), isMain()); + + if (isMain()) { + AWTContext context = (AWTContext) getApplication().getContext(); + context.setHeight(height); + context.setWidth(width); + } + + return frameTransfer; + } + + /** + * Create a new frame transfer. + * + * @param frameBuffer the frame buffer. + * @param width the width. + * @param height the height. + * @return the new frame transfer. + */ + protected AWTComponentRenderer createFrameTransfer(FrameBuffer frameBuffer, int width, int height) { + return new AWTComponentRenderer(getDestination(), getTransferMode(), isMain() ? null : frameBuffer, width, height); + } + + /** + * Reshape the current view port. + * + * @param width the width. + * @param height the height. + */ + protected void reshapeCurrentViewPort(int width, int height) { + + ViewPort viewPort = getViewPort(); + Camera camera = viewPort.getCamera(); + int cameraAngle = getCameraAngle(); + float aspect = (float) camera.getWidth() / camera.getHeight(); + + if (isMain()) { + getRenderManager().notifyReshape(width, height); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + return; + } + + camera.resize(width, height, true); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + + final List processors = viewPort.getProcessors(); + + boolean found = false; + Iterator iter = processors.iterator(); + while(!found && iter.hasNext()) { + if (!(iter.next() instanceof AWTFrameProcessor)) { + found = true; + } + } + + if (found) { + + FrameBuffer frameBuffer = new FrameBuffer(width, height, 1); + frameBuffer.setDepthBuffer(Image.Format.Depth); + frameBuffer.setColorBuffer(Image.Format.RGBA8); + frameBuffer.setSrgb(true); + + viewPort.setOutputFrameBuffer(frameBuffer); + } + + for (final SceneProcessor sceneProcessor : processors) { + if (!sceneProcessor.isInitialized()) { + sceneProcessor.initialize(renderManager, viewPort); + } else { + sceneProcessor.reshape(viewPort, width, height); + } + } + } + + /** + * Gets camera angle. + * + * @return the camera angle. + */ + protected int getCameraAngle() { + final String angle = System.getProperty("awt.frame.transfer.camera.angle", "45"); + return Integer.parseInt(angle); + } + + public TransferMode getTransferMode() { + return transferMode; + } + + public void setTransferMode(TransferMode transferMode) { + this.transferMode = transferMode; + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java new file mode 100644 index 000000000..07e3025b8 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTTaskExecutor.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + *

+ * This class is dedicated to the queuing of AWT related tasks and their execution. + * It's enable to store tasks that have to be executed within an AWT context and execute them at the specified time. + *

+ *

+ * This class is an AWT implementation of the JavaFX original code provided by Alexander Brui (see JME3-FX) + *

+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTTaskExecutor { + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final AWTTaskExecutor INSTANCE = new AWTTaskExecutor(); + + /** + * Get the instance of the executor. + * @return the instance of executor. + */ + public static AWTTaskExecutor getInstance() { + return INSTANCE; + } + + /** + * The list of waiting tasks. + */ + private final List waitTasks; + + private AWTTaskExecutor() { + waitTasks = new LinkedList(); + } + + public List getWaitingTasks(){ + return waitTasks; + } + + /** + * Add the given {@link Runnable runnable} to the list of planned executions. + * @param task the task to add. + * @see #execute() + */ + public void addToExecute(final Runnable task) { + lock.writeLock().lock(); + try { + waitTasks.add(task); + } catch (Exception e) { + // This try catch block enable to free the lock in case of any unexpected error. + } + lock.writeLock().unlock(); + } + + /** + * Execute all the tasks that are waiting. + * @see #addToExecute(Runnable) + */ + public void execute() { + + if (waitTasks.isEmpty()) return; + + lock.readLock().lock(); + + try { + for(Runnable runnable : waitTasks) { + runnable.run(); + } + } catch (Exception e) { + // This try catch block enable to free the lock in case of any unexpected error. + } + + waitTasks.clear(); + + lock.readLock().unlock(); + } +}