Merge branch 'master' into PBRisComing
This commit is contained in:
commit
aa067ef60d
@ -122,9 +122,13 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
|
|||||||
return Platform.Android_ARM6;
|
return Platform.Android_ARM6;
|
||||||
} else if (arch.contains("v7")) {
|
} else if (arch.contains("v7")) {
|
||||||
return Platform.Android_ARM7;
|
return Platform.Android_ARM7;
|
||||||
|
} else if (arch.contains("v8")) {
|
||||||
|
return Platform.Android_ARM8;
|
||||||
} else {
|
} else {
|
||||||
return Platform.Android_ARM5; // unknown ARM
|
return Platform.Android_ARM5; // unknown ARM
|
||||||
}
|
}
|
||||||
|
} else if (arch.contains("aarch")) {
|
||||||
|
return Platform.Android_ARM8;
|
||||||
} else {
|
} else {
|
||||||
return Platform.Android_Other;
|
return Platform.Android_Other;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ class BitmapTextPage extends Geometry {
|
|||||||
|
|
||||||
BitmapTextPage(BitmapFont font, boolean arrayBased, int page) {
|
BitmapTextPage(BitmapFont font, boolean arrayBased, int page) {
|
||||||
super("BitmapFont", new Mesh());
|
super("BitmapFont", new Mesh());
|
||||||
|
setRequiresUpdates(false);
|
||||||
setBatchHint(BatchHint.Never);
|
setBatchHint(BatchHint.Never);
|
||||||
if (font == null) {
|
if (font == null) {
|
||||||
throw new IllegalArgumentException("font cannot be null.");
|
throw new IllegalArgumentException("font cannot be null.");
|
||||||
|
@ -570,6 +570,35 @@ public class Node extends Spatial {
|
|||||||
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
|
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
|
||||||
// number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
|
// number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
|
||||||
// The idea is when there are few children, it can be too expensive to test boundingVolume first.
|
// The idea is when there are few children, it can be too expensive to test boundingVolume first.
|
||||||
|
/*
|
||||||
|
I'm removing this change until some issues can be addressed and I really
|
||||||
|
think it needs to be implemented a better way anyway.
|
||||||
|
|
||||||
|
First, it causes issues for anyone doing collideWith() with BoundingVolumes
|
||||||
|
and expecting it to trickle down to the children. For example, children
|
||||||
|
with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing
|
||||||
|
a collision check at the parent level then has to do a BoundingSphere to BoundingBox
|
||||||
|
collision which isn't resolved. (Having to come up with a collision point in that
|
||||||
|
case is tricky and the first sign that this is the wrong approach.)
|
||||||
|
|
||||||
|
Second, the rippling changes this caused to 'optimize' collideWith() for this
|
||||||
|
special use-case are another sign that this approach was a bit dodgy. The whole
|
||||||
|
idea of calculating a full collision just to see if the two shapes collide at all
|
||||||
|
is very wasteful.
|
||||||
|
|
||||||
|
A proper implementation should support a simpler boolean check that doesn't do
|
||||||
|
all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9%
|
||||||
|
of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much
|
||||||
|
faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
|
||||||
|
|
||||||
|
I don't have time to do it right now but I'll at least un-break a bunch of peoples'
|
||||||
|
code until it can be 'optimized' properly. Hopefully it's not too late to back out
|
||||||
|
the other dodgy ripples this caused. -pspeed (hindsight-expert ;))
|
||||||
|
|
||||||
|
Note: the code itself is relatively simple to implement but I don't have time to
|
||||||
|
a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast
|
||||||
|
enough to do all the time for > 1.
|
||||||
|
|
||||||
if (children.size() > 4)
|
if (children.size() > 4)
|
||||||
{
|
{
|
||||||
BoundingVolume bv = this.getWorldBound();
|
BoundingVolume bv = this.getWorldBound();
|
||||||
@ -578,6 +607,7 @@ public class Node extends Spatial {
|
|||||||
// collideWith without CollisionResults parameter used to avoid allocation when possible
|
// collideWith without CollisionResults parameter used to avoid allocation when possible
|
||||||
if (bv.collideWith(other) == 0) return 0;
|
if (bv.collideWith(other) == 0) return 0;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
for (Spatial child : children.getArray()){
|
for (Spatial child : children.getArray()){
|
||||||
total += child.collideWith(other, results);
|
total += child.collideWith(other, results);
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,10 @@ public abstract class JmeSystemDelegate {
|
|||||||
return false;
|
return false;
|
||||||
} else if (arch.equals("universal")) {
|
} else if (arch.equals("universal")) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (arch.equals("aarch32")) {
|
||||||
|
return false;
|
||||||
|
} else if (arch.equals("aarch64")) {
|
||||||
|
return true;
|
||||||
} else if (arch.equals("arm")) {
|
} else if (arch.equals("arm")) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,6 +88,11 @@ public enum Platform {
|
|||||||
*/
|
*/
|
||||||
Android_ARM7,
|
Android_ARM7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android ARM8
|
||||||
|
*/
|
||||||
|
Android_ARM8,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android x86
|
* Android x86
|
||||||
*/
|
*/
|
||||||
|
@ -309,8 +309,7 @@ public class HDRLoader implements AssetLoader {
|
|||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
dataStore.rewind();
|
dataStore.rewind();
|
||||||
//TODO, HDR color space? considered linear here
|
return new Image(pixelFormat, width, height, dataStore, ColorSpace.sRGB);
|
||||||
return new Image(pixelFormat, width, height, dataStore, ColorSpace.Linear);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object load(AssetInfo info) throws IOException {
|
public Object load(AssetInfo info) throws IOException {
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
package jme3test.network;
|
package jme3test.network;
|
||||||
|
|
||||||
import com.jme3.network.Client;
|
import com.jme3.network.Client;
|
||||||
|
import com.jme3.network.ClientStateListener;
|
||||||
|
import com.jme3.network.ErrorListener;
|
||||||
import com.jme3.network.Message;
|
import com.jme3.network.Message;
|
||||||
import com.jme3.network.MessageListener;
|
import com.jme3.network.MessageListener;
|
||||||
import com.jme3.network.Network;
|
import com.jme3.network.Network;
|
||||||
@ -51,11 +53,11 @@ import jme3test.network.TestChatServer.ChatMessage;
|
|||||||
*/
|
*/
|
||||||
public class TestChatClient extends JFrame {
|
public class TestChatClient extends JFrame {
|
||||||
|
|
||||||
private Client client;
|
private final Client client;
|
||||||
private JEditorPane chatLog;
|
private final JEditorPane chatLog;
|
||||||
private StringBuilder chatMessages = new StringBuilder();
|
private final StringBuilder chatMessages = new StringBuilder();
|
||||||
private JTextField nameField;
|
private final JTextField nameField;
|
||||||
private JTextField messageField;
|
private final JTextField messageField;
|
||||||
|
|
||||||
public TestChatClient(String host) throws IOException {
|
public TestChatClient(String host) throws IOException {
|
||||||
super("jME3 Test Chat Client - to:" + host);
|
super("jME3 Test Chat Client - to:" + host);
|
||||||
@ -90,7 +92,20 @@ public class TestChatClient extends JFrame {
|
|||||||
client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION,
|
client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION,
|
||||||
host, TestChatServer.PORT, TestChatServer.UDP_PORT);
|
host, TestChatServer.PORT, TestChatServer.UDP_PORT);
|
||||||
client.addMessageListener(new ChatHandler(), ChatMessage.class);
|
client.addMessageListener(new ChatHandler(), ChatMessage.class);
|
||||||
|
client.addClientStateListener(new ChatClientStateListener());
|
||||||
|
client.addErrorListener(new ChatErrorListener());
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
|
System.out.println("Started client:" + client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
System.out.println("Chat window closing.");
|
||||||
|
super.dispose();
|
||||||
|
if( client.isConnected() ) {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getString(Component owner, String title, String message, String initialValue) {
|
public static String getString(Component owner, String title, String message, String initialValue) {
|
||||||
@ -99,7 +114,12 @@ public class TestChatClient extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
TestChatServer.initializeClasses();
|
|
||||||
|
// Note: in JME 3.1 this is generally unnecessary as the server will
|
||||||
|
// send a message with all server-registered classes.
|
||||||
|
// TestChatServer.initializeClasses();
|
||||||
|
// Leaving the call commented out to be illustrative regarding the
|
||||||
|
// common old pattern.
|
||||||
|
|
||||||
// Grab a host string from the user
|
// Grab a host string from the user
|
||||||
String s = getString(null, "Host Info", "Enter chat host:", "localhost");
|
String s = getString(null, "Host Info", "Enter chat host:", "localhost");
|
||||||
@ -108,12 +128,23 @@ public class TestChatClient extends JFrame {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register a shutdown hook to get a message on the console when the
|
||||||
|
// app actually finishes
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Chat client is terminating.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
TestChatClient test = new TestChatClient(s);
|
TestChatClient test = new TestChatClient(s);
|
||||||
test.setVisible(true);
|
test.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ChatHandler implements MessageListener<Client> {
|
private class ChatHandler implements MessageListener<Client> {
|
||||||
|
|
||||||
|
@Override
|
||||||
public void messageReceived(Client source, Message m) {
|
public void messageReceived(Client source, Message m) {
|
||||||
ChatMessage chat = (ChatMessage) m;
|
ChatMessage chat = (ChatMessage) m;
|
||||||
|
|
||||||
@ -134,15 +165,50 @@ public class TestChatClient extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ChatClientStateListener implements ClientStateListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clientConnected(Client c) {
|
||||||
|
System.out.println("clientConnected(" + c + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clientDisconnected(Client c, DisconnectInfo info) {
|
||||||
|
System.out.println("clientDisconnected(" + c + "):" + info);
|
||||||
|
if( info != null ) {
|
||||||
|
// The connection was closed by the server
|
||||||
|
JOptionPane.showMessageDialog(rootPane,
|
||||||
|
info.reason,
|
||||||
|
"Connection Closed",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChatErrorListener implements ErrorListener<Client> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError( Client source, Throwable t ) {
|
||||||
|
System.out.println("handleError(" + source + ", " + t + ")");
|
||||||
|
JOptionPane.showMessageDialog(rootPane,
|
||||||
|
String.valueOf(t),
|
||||||
|
"Connection Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class SendAction extends AbstractAction {
|
private class SendAction extends AbstractAction {
|
||||||
|
|
||||||
private boolean reliable;
|
private final boolean reliable;
|
||||||
|
|
||||||
public SendAction(boolean reliable) {
|
public SendAction(boolean reliable) {
|
||||||
super(reliable ? "TCP" : "UDP");
|
super(reliable ? "TCP" : "UDP");
|
||||||
this.reliable = reliable;
|
this.reliable = reliable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
String name = nameField.getText();
|
String name = nameField.getText();
|
||||||
String message = messageField.getText();
|
String message = messageField.getText();
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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 jme3test.network;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines the server instance and a client instance into the
|
||||||
|
* same JVM to show an example of, and to test, a pattern like
|
||||||
|
* self-hosted multiplayer games.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class TestChatClientAndServer {
|
||||||
|
|
||||||
|
public static void main( String... args ) throws Exception {
|
||||||
|
|
||||||
|
System.out.println("Starting chat server...");
|
||||||
|
TestChatServer chatServer = new TestChatServer();
|
||||||
|
chatServer.start();
|
||||||
|
|
||||||
|
System.out.println("Waiting for connections on port:" + TestChatServer.PORT);
|
||||||
|
|
||||||
|
// Now launch a client
|
||||||
|
|
||||||
|
TestChatClient test = new TestChatClient("localhost");
|
||||||
|
test.setVisible(true);
|
||||||
|
|
||||||
|
// Register a shutdown hook to get a message on the console when the
|
||||||
|
// app actually finishes
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Client and server test is terminating.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep running basically forever or until the server
|
||||||
|
// shuts down
|
||||||
|
while( chatServer.isRunning() ) {
|
||||||
|
synchronized (chatServer) {
|
||||||
|
chatServer.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ package jme3test.network;
|
|||||||
import com.jme3.network.*;
|
import com.jme3.network.*;
|
||||||
import com.jme3.network.serializing.Serializable;
|
import com.jme3.network.serializing.Serializable;
|
||||||
import com.jme3.network.serializing.Serializer;
|
import com.jme3.network.serializing.Serializer;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple test chat server. When SM implements a set
|
* A simple test chat server. When SM implements a set
|
||||||
@ -51,6 +52,68 @@ public class TestChatServer {
|
|||||||
public static final int PORT = 5110;
|
public static final int PORT = 5110;
|
||||||
public static final int UDP_PORT = 5110;
|
public static final int UDP_PORT = 5110;
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private boolean isRunning;
|
||||||
|
|
||||||
|
public TestChatServer() throws IOException {
|
||||||
|
initializeClasses();
|
||||||
|
|
||||||
|
// Use this to test the client/server name version check
|
||||||
|
this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
|
||||||
|
|
||||||
|
ChatHandler handler = new ChatHandler();
|
||||||
|
server.addMessageListener(handler, ChatMessage.class);
|
||||||
|
|
||||||
|
server.addConnectionListener(new ChatConnectionListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void start() {
|
||||||
|
if( isRunning ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.start();
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void close() {
|
||||||
|
if( !isRunning ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gracefully let any connections know that the server is
|
||||||
|
// going down. Without this, their connections will simply
|
||||||
|
// error out.
|
||||||
|
for( HostedConnection conn : server.getConnections() ) {
|
||||||
|
conn.close("Server is shutting down.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000); // wait a couple beats to let the messages go out
|
||||||
|
} catch( InterruptedException e ) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
isRunning = false;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runCommand( HostedConnection conn, String user, String command ) {
|
||||||
|
if( "/shutdown".equals(command) ) {
|
||||||
|
server.broadcast(new ChatMessage("server", "Server is shutting down."));
|
||||||
|
close();
|
||||||
|
} else if( "/help".equals(command) ) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Chat commands:\n");
|
||||||
|
sb.append("/help - prints this message.\n");
|
||||||
|
sb.append("/shutdown - shuts down the server.");
|
||||||
|
server.broadcast(new ChatMessage("server", sb.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void initializeClasses() {
|
public static void initializeClasses() {
|
||||||
// Doing it here means that the client code only needs to
|
// Doing it here means that the client code only needs to
|
||||||
// call our initialize.
|
// call our initialize.
|
||||||
@ -58,44 +121,65 @@ public class TestChatServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
initializeClasses();
|
|
||||||
|
|
||||||
// Use this to test the client/server name version check
|
TestChatServer chatServer = new TestChatServer();
|
||||||
Server server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
|
chatServer.start();
|
||||||
server.start();
|
|
||||||
|
|
||||||
ChatHandler handler = new ChatHandler();
|
System.out.println("Waiting for connections on port:" + PORT);
|
||||||
server.addMessageListener(handler, ChatMessage.class);
|
|
||||||
|
|
||||||
// Keep running basically forever
|
// Keep running basically forever
|
||||||
synchronized (NAME) {
|
while( chatServer.isRunning ) {
|
||||||
NAME.wait();
|
synchronized (chatServer) {
|
||||||
|
chatServer.wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ChatHandler implements MessageListener<HostedConnection> {
|
private class ChatHandler implements MessageListener<HostedConnection> {
|
||||||
|
|
||||||
public ChatHandler() {
|
public ChatHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void messageReceived(HostedConnection source, Message m) {
|
public void messageReceived(HostedConnection source, Message m) {
|
||||||
if (m instanceof ChatMessage) {
|
if (m instanceof ChatMessage) {
|
||||||
// Keep track of the name just in case we
|
// Keep track of the name just in case we
|
||||||
// want to know it for some other reason later and it's
|
// want to know it for some other reason later and it's
|
||||||
// a good example of session data
|
// a good example of session data
|
||||||
source.setAttribute("name", ((ChatMessage) m).getName());
|
ChatMessage cm = (ChatMessage)m;
|
||||||
|
source.setAttribute("name", cm.getName());
|
||||||
|
|
||||||
|
// Check for a / command
|
||||||
|
if( cm.message.startsWith("/") ) {
|
||||||
|
runCommand(source, cm.name, cm.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("Broadcasting:" + m + " reliable:" + m.isReliable());
|
System.out.println("Broadcasting:" + m + " reliable:" + m.isReliable());
|
||||||
|
|
||||||
// Just rebroadcast... the reliable flag will stay the
|
// Just rebroadcast... the reliable flag will stay the
|
||||||
// same so if it came in on UDP it will go out on that too
|
// same so if it came in on UDP it will go out on that too
|
||||||
source.getServer().broadcast(m);
|
source.getServer().broadcast(cm);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Received odd message:" + m);
|
System.err.println("Received odd message:" + m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ChatConnectionListener implements ConnectionListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionAdded( Server server, HostedConnection conn ) {
|
||||||
|
System.out.println("connectionAdded(" + conn + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionRemoved(Server server, HostedConnection conn) {
|
||||||
|
System.out.println("connectionRemoved(" + conn + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public static class ChatMessage extends AbstractMessage {
|
public static class ChatMessage extends AbstractMessage {
|
||||||
|
|
||||||
@ -126,6 +210,7 @@ public class TestChatServer {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name + ":" + message;
|
return name + ":" + message;
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,24 @@ public class SerializerRegistrationsMessage extends AbstractMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerAll() {
|
public void registerAll() {
|
||||||
|
|
||||||
|
// See if we will have problems because our registry is locked
|
||||||
|
if( Serializer.isReadOnly() ) {
|
||||||
|
// Check to see if maybe we are executing this from the
|
||||||
|
// same JVM that sent the message, ie: client and server are running on
|
||||||
|
// the same JVM
|
||||||
|
// There could be more advanced checks than this but for now we'll
|
||||||
|
// assume that if the registry was compiled here then it means
|
||||||
|
// we are also the server process. Note that this wouldn't hold true
|
||||||
|
// under complicated examples where there are clients of one server
|
||||||
|
// that also run their own servers but realistically they would have
|
||||||
|
// to disable the ServerSerializerRegistrationsServer anyway.
|
||||||
|
if( compiled != null ) {
|
||||||
|
log.log( Level.INFO, "Skipping registration as registry is locked, presumably by a local server process.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for( Registration reg : registrations ) {
|
for( Registration reg : registrations ) {
|
||||||
log.log( Level.INFO, "Registering:{0}", reg);
|
log.log( Level.INFO, "Registering:{0}", reg);
|
||||||
reg.register();
|
reg.register();
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a given method should be executed asynchronously
|
||||||
|
* through the RMI service. This must annotate the method on the
|
||||||
|
* shared interface for it to have an effect. If reliable=false
|
||||||
|
* is specified then remote method invocation is done over UDP
|
||||||
|
* instead of TCP, ie: unreliably... but faster.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
@Retention(value=RUNTIME)
|
||||||
|
@Target(value=METHOD)
|
||||||
|
public @interface Asynchronous {
|
||||||
|
boolean reliable() default true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal type denoting the type of call to make when remotely
|
||||||
|
* invoking methods.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public enum CallType {
|
||||||
|
/**
|
||||||
|
* Caller will block until a response is received and returned.
|
||||||
|
*/
|
||||||
|
Synchronous,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caller does not block or wait for a response. The other end
|
||||||
|
* of the connection will also not send one.
|
||||||
|
*/
|
||||||
|
Asynchronous,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to asynchronous in that no response is expected or sent
|
||||||
|
* but differs in that the call will be sent over UDP and so may
|
||||||
|
* not make it to the other end.
|
||||||
|
*/
|
||||||
|
Unreliable
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.serializing.Serializable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal information about a shared class. This is the information
|
||||||
|
* that is sent over the wire for shared types.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public final class ClassInfo {
|
||||||
|
private String name;
|
||||||
|
private short typeId;
|
||||||
|
private MethodInfo[] methods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For serialization only.
|
||||||
|
*/
|
||||||
|
public ClassInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo( short typeId, Class type ) {
|
||||||
|
this.typeId = typeId;
|
||||||
|
this.name = type.getName();
|
||||||
|
this.methods = toMethodInfo(type, type.getMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class getType() {
|
||||||
|
try {
|
||||||
|
return Class.forName(name);
|
||||||
|
} catch( ClassNotFoundException e ) {
|
||||||
|
throw new RuntimeException("Error finding class for:" + this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getId() {
|
||||||
|
return typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo getMethod( short id ) {
|
||||||
|
return methods[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo getMethod( Method m ) {
|
||||||
|
for( MethodInfo mi : methods ) {
|
||||||
|
if( mi.matches(m) ) {
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo[] toMethodInfo( Class type, Method[] methods ) {
|
||||||
|
List<MethodInfo> result = new ArrayList<MethodInfo>();
|
||||||
|
short methodId = 0;
|
||||||
|
for( Method m : methods ) {
|
||||||
|
// Simple... add all methods exposed through the interface
|
||||||
|
result.add(new MethodInfo(methodId++, m));
|
||||||
|
}
|
||||||
|
return result.toArray(new MethodInfo[result.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo[] getMethods() {
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ClassInfo[" + name + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal registry of shared types and their ClassInfo and MethodInfo
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class ClassInfoRegistry {
|
||||||
|
|
||||||
|
//private final LoadingCache<Class, ClassInfo> cache; // Guava version
|
||||||
|
private final Map<Class, ClassInfo> cache = new HashMap<Class, ClassInfo>();
|
||||||
|
private final AtomicInteger nextClassId = new AtomicInteger();
|
||||||
|
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
public ClassInfoRegistry() {
|
||||||
|
//this.cache = CacheBuilder.newBuilder().build(new ClassInfoLoader()); // Guava version
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInfo getClassInfo( Class type ) {
|
||||||
|
//return cache.getUnchecked(type); // Guava version
|
||||||
|
|
||||||
|
// More complicated without guava
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
ClassInfo result = cache.get(type);
|
||||||
|
if( result != null ) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// Else we need to create it and store it... so grab the write
|
||||||
|
// lock
|
||||||
|
lock.readLock().unlock();
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
// Note: it's technically possible that a race with another thread
|
||||||
|
// asking for the same class already created one between our read unlock
|
||||||
|
// and our write lock. No matter as it's cheap to create one and does
|
||||||
|
// no harm. Code is simpler without the double-check.
|
||||||
|
result = new ClassInfo((short)nextClassId.getAndIncrement(), type);
|
||||||
|
cache.put(type, result);
|
||||||
|
|
||||||
|
// Regrab the read lock before leaving... kind of unnecessary but
|
||||||
|
// it makes the method cleaner and widens the gap of lock races.
|
||||||
|
// Downgrading a write lock to read is ok.
|
||||||
|
lock.readLock().lock();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
// Unlock the write lock while still holding onto read
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
would be more straight-forward with guava Guava version
|
||||||
|
private class ClassInfoLoader extends CacheLoader<Class, ClassInfo> {
|
||||||
|
@Override
|
||||||
|
public ClassInfo load( Class type ) {
|
||||||
|
return new ClassInfo((short)nextClassId.getAndIncrement(), type);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.serializing.Serializable;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import javax.jws.Oneway;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal information about shared methods. This is part of the data that
|
||||||
|
* is passed over the wire when an object is shared.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public final class MethodInfo {
|
||||||
|
|
||||||
|
public static final MethodInfo NULL_INFO = new MethodInfo();
|
||||||
|
|
||||||
|
private String representation;
|
||||||
|
private short id;
|
||||||
|
private CallType callType;
|
||||||
|
private transient Method method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For serialization only.
|
||||||
|
*/
|
||||||
|
public MethodInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodInfo( short id, Method m ) {
|
||||||
|
this.id = id;
|
||||||
|
this.method = m;
|
||||||
|
this.representation = methodToString(m);
|
||||||
|
this.callType = getCallType(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke( Object target, Object... parms ) {
|
||||||
|
try {
|
||||||
|
return method.invoke(target, parms);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallType getCallType() {
|
||||||
|
return callType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches( Method m ) {
|
||||||
|
return representation.equals(methodToString(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String methodToString( Method m ) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for( Class t : m.getParameterTypes() ) {
|
||||||
|
if( sb.length() > 0 )
|
||||||
|
sb.append(", ");
|
||||||
|
sb.append(t.getName());
|
||||||
|
}
|
||||||
|
return m.getReturnType().getName() + " " + m.getName() + "(" + sb + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CallType getCallType( Method m ) {
|
||||||
|
if( m.getReturnType() != Void.TYPE )
|
||||||
|
return CallType.Synchronous;
|
||||||
|
if( m.getAnnotation(Oneway.class) != null )
|
||||||
|
return CallType.Asynchronous;
|
||||||
|
if( m.getAnnotation(Asynchronous.class) == null )
|
||||||
|
return CallType.Synchronous;
|
||||||
|
|
||||||
|
Asynchronous async = m.getAnnotation(Asynchronous.class);
|
||||||
|
return async.reliable() ? CallType.Asynchronous : CallType.Unreliable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return representation.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals( Object o ) {
|
||||||
|
if( o == this ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if( o == null || o.getClass() != getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodInfo other = (MethodInfo)o;
|
||||||
|
return representation.equals(other.representation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MethodInfo[#" + getId() + ", callType=" + callType + ", " + representation + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally to remotely invoke methods on RMI shared objects.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class RemoteObjectHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private final RmiRegistry rmi;
|
||||||
|
private final byte channel;
|
||||||
|
private final short objectId;
|
||||||
|
private final ClassInfo typeInfo;
|
||||||
|
private final Map<Method, MethodInfo> methodIndex = new ConcurrentHashMap<Method, MethodInfo>();
|
||||||
|
|
||||||
|
public RemoteObjectHandler( RmiRegistry rmi, byte channel, short objectId, ClassInfo typeInfo ) {
|
||||||
|
this.rmi = rmi;
|
||||||
|
this.channel = channel;
|
||||||
|
this.objectId = objectId;
|
||||||
|
this.typeInfo = typeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MethodInfo getMethodInfo( Method method ) {
|
||||||
|
MethodInfo mi = methodIndex.get(method);
|
||||||
|
if( mi == null ) {
|
||||||
|
mi = typeInfo.getMethod(method);
|
||||||
|
if( mi == null ) {
|
||||||
|
mi = MethodInfo.NULL_INFO;
|
||||||
|
}
|
||||||
|
methodIndex.put(method, mi);
|
||||||
|
}
|
||||||
|
return mi == MethodInfo.NULL_INFO ? null : mi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object o, Method method, Object[] os) throws Throwable {
|
||||||
|
MethodInfo mi = getMethodInfo(method);
|
||||||
|
if( mi == null ) {
|
||||||
|
// Try to invoke locally
|
||||||
|
return method.invoke(this, os);
|
||||||
|
}
|
||||||
|
return rmi.invokeRemote(channel, objectId, mi.getId(), mi.getCallType(), os);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RemoteObject[#" + objectId + ", " + typeInfo.getName() + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.MessageConnection;
|
||||||
|
import com.jme3.network.service.AbstractClientService;
|
||||||
|
import com.jme3.network.service.ClientServiceManager;
|
||||||
|
import com.jme3.network.service.rpc.RpcClientService;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that can be added to the client to support a simple
|
||||||
|
* shared objects protocol.
|
||||||
|
*
|
||||||
|
* <p>Objects are shared by adding them to the RmiRegistry with one of the
|
||||||
|
* share() methods. Shared objects must have a separate interface and implementation.
|
||||||
|
* The interface is what the other end of the connection will use to interact
|
||||||
|
* with the object and that interface class must be available on both ends of
|
||||||
|
* the connection. The implementing class need only be on the sharing end.</p>
|
||||||
|
*
|
||||||
|
* <p>Shared objects can be accessed on the other end of the connection by
|
||||||
|
* using one of the RmiRegistry's getRemoteObject() methods. These can be
|
||||||
|
* used to lookup an object by class if it is a shared singleton or by name
|
||||||
|
* if it was registered with a name.</p>
|
||||||
|
*
|
||||||
|
* <p>Note: This RMI implementation is not as advanced as Java's regular
|
||||||
|
* RMI as it won't marshall shared references, ie: you can't pass
|
||||||
|
* a shared objects as an argument to another shared object's method.</p>
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class RmiClientService extends AbstractClientService {
|
||||||
|
|
||||||
|
private RpcClientService rpc;
|
||||||
|
private byte defaultChannel;
|
||||||
|
private short rmiObjectId;
|
||||||
|
private RmiRegistry rmi;
|
||||||
|
private volatile boolean isStarted = false;
|
||||||
|
|
||||||
|
private final List<ObjectInfo> pending = new ArrayList<ObjectInfo>();
|
||||||
|
|
||||||
|
public RmiClientService() {
|
||||||
|
this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RmiClientService( short rmiObjectId, byte defaultChannel ) {
|
||||||
|
this.defaultChannel = defaultChannel;
|
||||||
|
this.rmiObjectId = rmiObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the specified object with the server and associates it with the
|
||||||
|
* specified type. Objects shared in this way are available in the connection-specific
|
||||||
|
* RMI registry on the server and are not available to other connections.
|
||||||
|
*/
|
||||||
|
public <T> void share( T object, Class<? super T> type ) {
|
||||||
|
share(defaultChannel, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the specified object with the server and associates it with the
|
||||||
|
* specified type. Objects shared in this way are available in the connection-specific
|
||||||
|
* RMI registry on the server and are not available to other connections.
|
||||||
|
* All object related communication will be done over the specified connection
|
||||||
|
* channel.
|
||||||
|
*/
|
||||||
|
public <T> void share( byte channel, T object, Class<? super T> type ) {
|
||||||
|
share(channel, type.getName(), object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the specified object with the server and associates it with the
|
||||||
|
* specified name. Objects shared in this way are available in the connection-specific
|
||||||
|
* RMI registry on the server and are not available to other connections.
|
||||||
|
*/
|
||||||
|
public <T> void share( String name, T object, Class<? super T> type ) {
|
||||||
|
share(defaultChannel, name, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the specified object with the server and associates it with the
|
||||||
|
* specified name. Objects shared in this way are available in the connection-specific
|
||||||
|
* RMI registry on the server and are not available to other connections.
|
||||||
|
* All object related communication will be done over the specified connection
|
||||||
|
* channel.
|
||||||
|
*/
|
||||||
|
public <T> void share( byte channel, String name, T object, Class<? super T> type ) {
|
||||||
|
if( !isStarted ) {
|
||||||
|
synchronized(pending) {
|
||||||
|
if( !isStarted ) {
|
||||||
|
pending.add(new ObjectInfo(channel, name, object, type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else we can add it directly.
|
||||||
|
rmi.share(channel, name, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up a remote object on the server by type and returns a local proxy to the
|
||||||
|
* remote object that was shared on the other end of the network connection.
|
||||||
|
*/
|
||||||
|
public <T> T getRemoteObject( Class<T> type ) {
|
||||||
|
return rmi.getRemoteObject(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up a remote object on the server by name and returns a local proxy to the
|
||||||
|
* remote object that was shared on the other end of the network connection.
|
||||||
|
*/
|
||||||
|
public <T> T getRemoteObject( String name, Class<T> type ) {
|
||||||
|
return rmi.getRemoteObject(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onInitialize( ClientServiceManager s ) {
|
||||||
|
rpc = getService(RpcClientService.class);
|
||||||
|
if( rpc == null ) {
|
||||||
|
throw new RuntimeException("RmiClientService requires RpcClientService");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register it now so that it is available when the
|
||||||
|
// server starts to send us stuff. Waiting until start()
|
||||||
|
// is too late in this case.
|
||||||
|
rmi = new RmiRegistry(rpc.getRpcConnection(), rmiObjectId, defaultChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
// Register all of the classes that have been waiting.
|
||||||
|
synchronized(pending) {
|
||||||
|
for( ObjectInfo info : pending ) {
|
||||||
|
rmi.share(info.channel, info.name, info.object, info.type);
|
||||||
|
}
|
||||||
|
pending.clear();
|
||||||
|
isStarted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjectInfo {
|
||||||
|
byte channel;
|
||||||
|
String name;
|
||||||
|
Object object;
|
||||||
|
Class type;
|
||||||
|
|
||||||
|
public ObjectInfo( byte channel, String name, Object object, Class type ) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.name = name;
|
||||||
|
this.object = object;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ObjectInfo[" + channel + ", " + name + ", " + object + ", " + type + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of the current connection performing a particular
|
||||||
|
* RMI call. RMI-based services can use this to find out which
|
||||||
|
* connection is calling a particular method without having to
|
||||||
|
* pass additional problematic data on the method calls.
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class RmiContext {
|
||||||
|
private static final ThreadLocal<HostedConnection> connection = new ThreadLocal<HostedConnection>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HostedConnection that is responsible for any
|
||||||
|
* RMI-related calls on this thread.
|
||||||
|
*/
|
||||||
|
public static HostedConnection getRmiConnection() {
|
||||||
|
return connection.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setRmiConnection( HostedConnection conn ) {
|
||||||
|
connection.set(conn);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection;
|
||||||
|
import com.jme3.network.MessageConnection;
|
||||||
|
import com.jme3.network.Server;
|
||||||
|
import com.jme3.network.serializing.Serializer;
|
||||||
|
import com.jme3.network.service.AbstractHostedService;
|
||||||
|
import com.jme3.network.service.HostedServiceManager;
|
||||||
|
import com.jme3.network.service.rpc.RpcHostedService;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that can be added to the host to support a simple
|
||||||
|
* shared objects protocol.
|
||||||
|
*
|
||||||
|
* <p>Objects are shared by adding them to the RmiRegistry with one of the
|
||||||
|
* share() methods. Shared objects must have a separate interface and implementation.
|
||||||
|
* The interface is what the other end of the connection will use to interact
|
||||||
|
* with the object and that interface class must be available on both ends of
|
||||||
|
* the connection. The implementing class need only be on the sharing end.</p>
|
||||||
|
*
|
||||||
|
* <p>Shared objects can be accessed on the other end of the connection by
|
||||||
|
* using one of the RmiRegistry's getRemoteObject() methods. These can be
|
||||||
|
* used to lookup an object by class if it is a shared singleton or by name
|
||||||
|
* if it was registered with a name.</p>
|
||||||
|
*
|
||||||
|
* <p>On the hosting side, a special shardGlobal() method is provided that
|
||||||
|
* will register shared objects that will automatically be provided to every
|
||||||
|
* new joining client and they will all be calling the same server-side instance.
|
||||||
|
* Normally, shared objects themselves are connection specific and handled
|
||||||
|
* at the connection layer. The shareGlobal() space is a way to have global
|
||||||
|
* resources passed directly though the need is relatively rare.</p>
|
||||||
|
*
|
||||||
|
* <p>Note: This RMI implementation is not as advanced as Java's regular
|
||||||
|
* RMI as it won't marshall shared references, ie: you can't pass
|
||||||
|
* a shared objects as an argument to another shared object's method.</p>
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class RmiHostedService extends AbstractHostedService {
|
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(RpcHostedService.class.getName());
|
||||||
|
|
||||||
|
public static final String ATTRIBUTE_NAME = "rmi";
|
||||||
|
|
||||||
|
private RpcHostedService rpcService;
|
||||||
|
private short rmiId;
|
||||||
|
private byte defaultChannel;
|
||||||
|
private boolean autoHost;
|
||||||
|
private final Map<String, GlobalShare> globalShares = new ConcurrentHashMap<String, GlobalShare>();
|
||||||
|
|
||||||
|
public RmiHostedService() {
|
||||||
|
this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) {
|
||||||
|
this.rmiId = rmiId;
|
||||||
|
this.defaultChannel = defaultChannel;
|
||||||
|
this.autoHost = autoHost;
|
||||||
|
|
||||||
|
Serializer.registerClasses(ClassInfo.class, MethodInfo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares a server-wide object associated with the specified type. All connections
|
||||||
|
* with RMI hosting started will have access to this shared object as soon as they
|
||||||
|
* connect and they will all share the same instance. It is up to the shared object
|
||||||
|
* to handle any multithreading that might be required.
|
||||||
|
*/
|
||||||
|
public <T> void shareGlobal( T object, Class<? super T> type ) {
|
||||||
|
shareGlobal(defaultChannel, type.getName(), object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares a server-wide object associated with the specified name. All connections
|
||||||
|
* with RMI hosting started will have access to this shared object as soon as they
|
||||||
|
* connect and they will all share the same instance. It is up to the shared object
|
||||||
|
* to handle any multithreading that might be required.
|
||||||
|
*/
|
||||||
|
public <T> void shareGlobal( String name, T object, Class<? super T> type ) {
|
||||||
|
shareGlobal(defaultChannel, name, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares a server-wide object associated with the specified name over the specified
|
||||||
|
* channel. All connections with RMI hosting started will have access to this shared
|
||||||
|
* object as soon as they connect and they will all share the same instance. It is up
|
||||||
|
* to the shared object to handle any multithreading that might be required.
|
||||||
|
* All network communcation associated with the shared object will be done over
|
||||||
|
* the specified channel.
|
||||||
|
*/
|
||||||
|
public <T> void shareGlobal( byte channel, String name, T object, Class<? super T> type ) {
|
||||||
|
GlobalShare share = new GlobalShare(channel, object, type);
|
||||||
|
GlobalShare existing = globalShares.put(name, share);
|
||||||
|
if( existing != null ) {
|
||||||
|
// Shouldn't need to do anything actually.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all of the children
|
||||||
|
for( HostedConnection conn : getServer().getConnections() ) {
|
||||||
|
RmiRegistry child = getRmiRegistry(conn);
|
||||||
|
if( child == null ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
child.share(channel, name, object, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true if all new connections should automatically have RMI hosting started.
|
||||||
|
* Set to false if the game-specific connection setup will call startHostingOnConnection()
|
||||||
|
* after some connection setup is done (for example, logging in). Note: generally
|
||||||
|
* is is safe to autohost RMI as long as callers are careful about what they've added
|
||||||
|
* using shareGlobal(). One reasonable use-case is to shareGlobal() some kind of login
|
||||||
|
* service and nothing else. All other shared objects would then be added as connection
|
||||||
|
* specific objects during successful login processing.
|
||||||
|
*/
|
||||||
|
public void setAutoHost( boolean b ) {
|
||||||
|
this.autoHost = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if RMI hosting is automatically started for all new connections.
|
||||||
|
*/
|
||||||
|
public boolean getAutoHost() {
|
||||||
|
return autoHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RMI registry for the specific HostedConection. Each connection
|
||||||
|
* has its own registry with its own connection-specific shared objects.
|
||||||
|
*/
|
||||||
|
public RmiRegistry getRmiRegistry( HostedConnection hc ) {
|
||||||
|
return hc.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up RMI hosting services for the hosted connection allowing
|
||||||
|
* getRmiRegistry() to return a valid RmiRegistry object.
|
||||||
|
* This method is called automatically for all new connections if
|
||||||
|
* autohost is set to true.
|
||||||
|
*/
|
||||||
|
public void startHostingOnConnection( HostedConnection hc ) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
|
||||||
|
}
|
||||||
|
RmiRegistry rmi = new RmiRegistry(hc, rpcService.getRpcConnection(hc),
|
||||||
|
rmiId, defaultChannel);
|
||||||
|
hc.setAttribute(ATTRIBUTE_NAME, rmi);
|
||||||
|
|
||||||
|
// Register any global shares
|
||||||
|
for( Map.Entry<String, GlobalShare> e : globalShares.entrySet() ) {
|
||||||
|
GlobalShare share = e.getValue();
|
||||||
|
rmi.share(share.channel, e.getKey(), share.object, share.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any RMI hosting services associated with the specified
|
||||||
|
* connection. Calls to getRmiRegistry() will return null for
|
||||||
|
* this connection.
|
||||||
|
* This method is called automatically for all leaving connections if
|
||||||
|
* autohost is set to true.
|
||||||
|
*/
|
||||||
|
public void stopHostingOnConnection( HostedConnection hc ) {
|
||||||
|
RmiRegistry rmi = hc.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if( rmi == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc);
|
||||||
|
}
|
||||||
|
hc.setAttribute(ATTRIBUTE_NAME, null);
|
||||||
|
//rpc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onInitialize( HostedServiceManager s ) {
|
||||||
|
this.rpcService = getService(RpcHostedService.class);
|
||||||
|
if( rpcService == null ) {
|
||||||
|
throw new RuntimeException("RmiHostedService requires RpcHostedService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called internally when a new connection is detected for
|
||||||
|
* the server. If the current autoHost property is true then
|
||||||
|
* startHostingOnConnection(hc) is called.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void connectionAdded(Server server, HostedConnection hc) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
|
||||||
|
}
|
||||||
|
if( autoHost ) {
|
||||||
|
startHostingOnConnection(hc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called internally when an existing connection is leaving
|
||||||
|
* the server. If the current autoHost property is true then
|
||||||
|
* stopHostingOnConnection(hc) is called.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void connectionRemoved(Server server, HostedConnection hc) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
|
||||||
|
}
|
||||||
|
if( autoHost ) {
|
||||||
|
stopHostingOnConnection(hc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GlobalShare {
|
||||||
|
byte channel;
|
||||||
|
Object object;
|
||||||
|
Class type;
|
||||||
|
|
||||||
|
public GlobalShare( byte channel, Object object, Class type ) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.object = object;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,387 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 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.network.service.rmi;
|
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection;
|
||||||
|
import com.jme3.network.MessageConnection;
|
||||||
|
import com.jme3.network.service.rpc.RpcConnection;
|
||||||
|
import com.jme3.network.service.rpc.RpcHandler;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Paul Speed
|
||||||
|
*/
|
||||||
|
public class RmiRegistry {
|
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(RmiRegistry.class.getName());
|
||||||
|
|
||||||
|
// RPC IDs for calling our remote endpoint
|
||||||
|
private static final short NEW_CLASS = 0;
|
||||||
|
private static final short ADD_OBJECT = 1;
|
||||||
|
private static final short REMOVE_OBJECT = 2;
|
||||||
|
|
||||||
|
private RpcConnection rpc;
|
||||||
|
private short rmiId;
|
||||||
|
private byte defaultChannel;
|
||||||
|
private final RmiHandler rmiHandler = new RmiHandler();
|
||||||
|
private final ClassInfoRegistry classCache = new ClassInfoRegistry();
|
||||||
|
private final AtomicInteger nextObjectId = new AtomicInteger();
|
||||||
|
|
||||||
|
private final ObjectIndex<SharedObject> local = new ObjectIndex<SharedObject>();
|
||||||
|
private final ObjectIndex<Object> remote = new ObjectIndex<Object>();
|
||||||
|
|
||||||
|
// Only used on the server to provide thread-local context for
|
||||||
|
// local RMI calls.
|
||||||
|
private HostedConnection context;
|
||||||
|
|
||||||
|
public RmiRegistry( RpcConnection rpc, short rmiId, byte defaultChannel ) {
|
||||||
|
this(null, rpc, rmiId, defaultChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RmiRegistry( HostedConnection context, RpcConnection rpc, short rmiId, byte defaultChannel ) {
|
||||||
|
this.context = context;
|
||||||
|
this.rpc = rpc;
|
||||||
|
this.rmiId = rmiId;
|
||||||
|
this.defaultChannel = defaultChannel;
|
||||||
|
rpc.registerHandler(rmiId, rmiHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the specified object to the other end of the connection as
|
||||||
|
* the specified interface type. The object can be looked up by type
|
||||||
|
* on the other end.
|
||||||
|
*/
|
||||||
|
public <T> void share( T object, Class<? super T> type ) {
|
||||||
|
share(defaultChannel, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes, through a specific connection channel, the specified object
|
||||||
|
* to the other end of the connection as the specified interface type.
|
||||||
|
* The object can be looked up by type on the other end.
|
||||||
|
* The specified channel will be used for all network communication
|
||||||
|
* specific to this object.
|
||||||
|
*/
|
||||||
|
public <T> void share( byte channel, T object, Class<? super T> type ) {
|
||||||
|
share(channel, type.getName(), object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the specified object to the other end of the connection as
|
||||||
|
* the specified interface type and associates it with the specified name.
|
||||||
|
* The object can be looked up by the associated name on the other end of
|
||||||
|
* the connection.
|
||||||
|
*/
|
||||||
|
public <T> void share( String name, T object, Class<? super T> type ) {
|
||||||
|
share(defaultChannel, name, object, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes, through a specific connection channel, the specified object to
|
||||||
|
* the other end of the connection as the specified interface type and associates
|
||||||
|
* it with the specified name.
|
||||||
|
* The object can be looked up by the associated name on the other end of
|
||||||
|
* the connection.
|
||||||
|
* The specified channel will be used for all network communication
|
||||||
|
* specific to this object.
|
||||||
|
*/
|
||||||
|
public <T> void share( byte channel, String name, T object, Class<? super T> type ) {
|
||||||
|
|
||||||
|
ClassInfo typeInfo = classCache.getClassInfo(type);
|
||||||
|
|
||||||
|
local.lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
|
||||||
|
// First see if we've told the remote end about this class
|
||||||
|
// before
|
||||||
|
if( local.classes.put(typeInfo.getId(), typeInfo) == null ) {
|
||||||
|
// It's new
|
||||||
|
rpc.callAsync(defaultChannel, rmiId, NEW_CLASS, typeInfo);
|
||||||
|
|
||||||
|
// Because type info IDs are global to the class cache,
|
||||||
|
// we could in theory keep a global index that we broadcast
|
||||||
|
// on first connection setup... we need only prepopulate
|
||||||
|
// the index in that case.
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we already shared an object under that name
|
||||||
|
SharedObject existing = local.byName.remove(name);
|
||||||
|
if( existing != null ) {
|
||||||
|
local.byId.remove(existing.objectId);
|
||||||
|
rpc.removeHandler(existing.objectId, rmiHandler);
|
||||||
|
|
||||||
|
// Need to delete the old one from the remote end
|
||||||
|
rpc.callAsync(defaultChannel, rmiId, REMOVE_OBJECT, existing.objectId);
|
||||||
|
|
||||||
|
// We don't reuse the ID because it's kind of dangerous.
|
||||||
|
// Churning through a new ID is our safety net for accidents.
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedObject newShare = new SharedObject(name, object, type, typeInfo);
|
||||||
|
local.byName.put(name, newShare);
|
||||||
|
local.byId.put(newShare.objectId, newShare);
|
||||||
|
|
||||||
|
// Make sure we are setup to receive the remote method calls through
|
||||||
|
// the RPC service
|
||||||
|
rpc.registerHandler(newShare.objectId, rmiHandler);
|
||||||
|
|
||||||
|
// Let the other end know
|
||||||
|
rpc.callAsync(defaultChannel, rmiId, ADD_OBJECT, channel, newShare.objectId, name, typeInfo.getId());
|
||||||
|
|
||||||
|
// We send the ADD_OBJECT to the other end before releasing the
|
||||||
|
// lock to avoid a potential inconsistency if two threads try to
|
||||||
|
// jam the same name at the same time. Otherwise, if the timing were
|
||||||
|
// right, the remove for one object could get there before its add.
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
local.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a local object that was previously registered with share() using
|
||||||
|
* just type registration.
|
||||||
|
*/
|
||||||
|
public <T> T getLocalObject( Class<T> type ) {
|
||||||
|
return getLocalObject(type.getName(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a local object that was previously registered with share() using
|
||||||
|
* name registration.
|
||||||
|
*/
|
||||||
|
public <T> T getLocalObject( String name, Class<T> type ) {
|
||||||
|
local.lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return type.cast(local.byName.get(name));
|
||||||
|
} finally {
|
||||||
|
local.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up a remote object by type and returns a local proxy to the remote object
|
||||||
|
* that was shared on the other end of the network connection. If this is called
|
||||||
|
* from a client then it is accessing a shared object registered on the server.
|
||||||
|
* If this is called from the server then it is accessing a shared object registered
|
||||||
|
* on the client.
|
||||||
|
*/
|
||||||
|
public <T> T getRemoteObject( Class<T> type ) {
|
||||||
|
return getRemoteObject(type.getName(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up a remote object by name and returns a local proxy to the remote object
|
||||||
|
* that was shared on the other end of the network connection. If this is called
|
||||||
|
* from a client then it is accessing a shared object registered on the server.
|
||||||
|
* If this is called from the server then it is accessing a shared object registered
|
||||||
|
* on the client.
|
||||||
|
*/
|
||||||
|
public <T> T getRemoteObject( String name, Class<T> type ) {
|
||||||
|
remote.lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return type.cast(remote.byName.get(name));
|
||||||
|
} finally {
|
||||||
|
remote.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRemoteClass( ClassInfo info ) {
|
||||||
|
if( remote.classes.put(info.getId(), info) != null ) {
|
||||||
|
throw new RuntimeException("Error class already exists for ID:" + info.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeRemoteObject( short objectId ) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.log(Level.FINEST, "removeRemoteObject({0})", objectId);
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Removal not yet implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRemoteObject( byte channel, short objectId, String name, ClassInfo typeInfo ) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.finest("addRemoveObject(" + objectId + ", " + name + ", " + typeInfo + ")");
|
||||||
|
}
|
||||||
|
remote.lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Object existing = remote.byName.get(name);
|
||||||
|
if( existing != null ) {
|
||||||
|
throw new RuntimeException("Object already registered for:" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteObjectHandler remoteHandler = new RemoteObjectHandler(this, channel, objectId, typeInfo);
|
||||||
|
|
||||||
|
Object remoteObject = Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||||
|
new Class[] {typeInfo.getType()},
|
||||||
|
remoteHandler);
|
||||||
|
|
||||||
|
remote.byName.put(name, remoteObject);
|
||||||
|
remote.byId.put(objectId, remoteObject);
|
||||||
|
} finally {
|
||||||
|
remote.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object invokeRemote( byte channel, short objectId, short procId, CallType callType, Object[] args ) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.finest("invokeRemote(" + channel + ", " + objectId + ", " + procId + ", "
|
||||||
|
+ callType + ", " + (args == null ? "null" : Arrays.asList(args)) + ")");
|
||||||
|
}
|
||||||
|
switch( callType ) {
|
||||||
|
case Asynchronous:
|
||||||
|
log.finest("Sending reliable asynchronous.");
|
||||||
|
rpc.callAsync(channel, objectId, procId, args);
|
||||||
|
return null;
|
||||||
|
case Unreliable:
|
||||||
|
log.finest("Sending unreliable asynchronous.");
|
||||||
|
rpc.callAsync((byte)MessageConnection.CHANNEL_DEFAULT_UNRELIABLE, objectId, procId, args);
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
case Synchronous:
|
||||||
|
log.finest("Sending synchronous.");
|
||||||
|
Object result = rpc.callAndWait(channel, objectId, procId, args);
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.finest("->got:" + result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle remote object registry updates from the other end.
|
||||||
|
*/
|
||||||
|
protected void rmiUpdate( short procId, Object[] args ) {
|
||||||
|
if( log.isLoggable(Level.FINEST) ) {
|
||||||
|
log.finest("rmiUpdate(" + procId + ", " + Arrays.asList(args) + ")");
|
||||||
|
}
|
||||||
|
switch( procId ) {
|
||||||
|
case NEW_CLASS:
|
||||||
|
addRemoteClass((ClassInfo)args[0]);
|
||||||
|
break;
|
||||||
|
case REMOVE_OBJECT:
|
||||||
|
removeRemoteObject((Short)args[0]);
|
||||||
|
break;
|
||||||
|
case ADD_OBJECT:
|
||||||
|
ClassInfo info = remote.classes.get((Short)args[3]);
|
||||||
|
addRemoteObject((Byte)args[0], (Short)args[1], (String)args[2], info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the actual remote object method calls.
|
||||||
|
*/
|
||||||
|
protected Object invokeLocal( short objectId, short procId, Object[] args ) {
|
||||||
|
// Actually could use a regular concurrent map for this
|
||||||
|
|
||||||
|
// Only lock the local registry during lookup and
|
||||||
|
// not invocation. It prevents a deadlock if the invoked method
|
||||||
|
// tries to share an object. It should be safe.
|
||||||
|
SharedObject share = local.byId.get(objectId);
|
||||||
|
local.lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
share = local.byId.get(objectId);
|
||||||
|
} finally {
|
||||||
|
local.lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RmiContext.setRmiConnection(context);
|
||||||
|
return share.invoke(procId, args);
|
||||||
|
} finally {
|
||||||
|
RmiContext.setRmiConnection(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SharedObject {
|
||||||
|
private final short objectId;
|
||||||
|
private final String name;
|
||||||
|
private final Object object;
|
||||||
|
private final Class type;
|
||||||
|
private final ClassInfo classInfo;
|
||||||
|
|
||||||
|
public SharedObject( String name, Object object, Class type, ClassInfo classInfo ) {
|
||||||
|
this.objectId = (short)nextObjectId.incrementAndGet();
|
||||||
|
this.name = name;
|
||||||
|
this.object = object;
|
||||||
|
this.type = type;
|
||||||
|
this.classInfo = classInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke( short procId, Object[] args ) {
|
||||||
|
return classInfo.getMethod(procId).invoke(object, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RmiHandler implements RpcHandler {
|
||||||
|
@Override
|
||||||
|
public Object call( RpcConnection conn, short objectId, short procId, Object... args ) {
|
||||||
|
if( objectId == rmiId ) {
|
||||||
|
rmiUpdate(procId, args);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return invokeLocal(objectId, procId, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps a coincident index between short ID, name, and related class info.
|
||||||
|
* There will be one of these to track our local objects and one to track
|
||||||
|
* the remote objects and a lock that can guard them.
|
||||||
|
*/
|
||||||
|
private class ObjectIndex<T> {
|
||||||
|
final Map<String, T> byName = new HashMap<String, T>();
|
||||||
|
final Map<Short, T> byId = new HashMap<Short, T>();
|
||||||
|
final Map<Short, ClassInfo> classes = new HashMap<Short, ClassInfo>();
|
||||||
|
final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
public ObjectIndex() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +39,8 @@ import com.jme3.network.message.SerializerRegistrationsMessage;
|
|||||||
import com.jme3.network.serializing.Serializer;
|
import com.jme3.network.serializing.Serializer;
|
||||||
import com.jme3.network.service.AbstractClientService;
|
import com.jme3.network.service.AbstractClientService;
|
||||||
import com.jme3.network.service.ClientServiceManager;
|
import com.jme3.network.service.ClientServiceManager;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,18 +51,26 @@ import com.jme3.network.service.ClientServiceManager;
|
|||||||
public class ClientSerializerRegistrationsService extends AbstractClientService
|
public class ClientSerializerRegistrationsService extends AbstractClientService
|
||||||
implements MessageListener<Client> {
|
implements MessageListener<Client> {
|
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInitialize( ClientServiceManager serviceManager ) {
|
protected void onInitialize( ClientServiceManager serviceManager ) {
|
||||||
// Make sure our message type is registered
|
|
||||||
|
// Make sure our message type is registered if it isn't already
|
||||||
|
if( Serializer.getExactSerializerRegistration(SerializerRegistrationsMessage.class) == null ) {
|
||||||
// This is the minimum we'd need just to be able to register
|
// This is the minimum we'd need just to be able to register
|
||||||
// the rest... otherwise we can't even receive this message.
|
// the rest... otherwise we can't even receive this message.
|
||||||
Serializer.registerClass(SerializerRegistrationsMessage.class);
|
Serializer.registerClass(SerializerRegistrationsMessage.class);
|
||||||
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
|
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
|
||||||
|
} else {
|
||||||
|
log.log(Level.INFO, "Skipping registration of SerializerRegistrationsMessage.");
|
||||||
|
}
|
||||||
|
|
||||||
// Add our listener for that message type
|
// Add our listener for that message type
|
||||||
serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class);
|
serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void messageReceived( Client source, Message m ) {
|
public void messageReceived( Client source, Message m ) {
|
||||||
// We only wait for one kind of message...
|
// We only wait for one kind of message...
|
||||||
SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m;
|
SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user