/*
* Copyright 2013-2016 consulo.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package consulo.unity3d.run.debugger;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ExceptionUtil;
/**
* @author VISTALL
* @since 10.11.14
*/
public class UnityPlayerService implements ApplicationComponent
{
private static final Logger LOGGER = Logger.getInstance(UnityPlayerService.class);
public interface UpdateListener extends EventListener
{
void update(@NotNull List<UnityPlayer> unityPlayers);
}
@NotNull
public static UnityPlayerService getInstance()
{
return ApplicationManager.getApplication().getComponent(UnityPlayerService.class);
}
private static final int[] ourPorts = {
54997,
34997,
57997,
58997
};
private static final String ourUdpGroupIp = "225.0.0.222";
private List<UnityUdpThread> myThreads = new ArrayList<UnityUdpThread>();
private ConcurrentMap<UnityPlayer, UnityPlayer> myPlayers = new ConcurrentHashMap<UnityPlayer, UnityPlayer>();
private EventDispatcher<UpdateListener> myUpdateListenerEventDispatcher = EventDispatcher.create(UpdateListener.class);
private ScheduledExecutorService myExecutorService = Executors.newScheduledThreadPool(1);
@Override
public void initComponent()
{
try
{
int succBinds = 0;
int failBinds = 0;
InetAddress groupAddress = InetAddress.getByName(ourUdpGroupIp);
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while(networkInterfaces.hasMoreElements())
{
NetworkInterface networkInterface = networkInterfaces.nextElement();
if(!haveIp4Address(networkInterface))
{
continue;
}
for(int playerMulticastPort : ourPorts)
{
final MulticastSocket serverSocket;
try
{
serverSocket = new MulticastSocket(playerMulticastPort);
serverSocket.setReuseAddress(true);
serverSocket.setBroadcast(true);
serverSocket.setNetworkInterface(networkInterface);
serverSocket.joinGroup(groupAddress);
UnityUdpThread udpThread = new UnityUdpThread(this, serverSocket, playerMulticastPort, networkInterface);
udpThread.start();
myThreads.add(udpThread);
succBinds++;
UnityPlayerService.LOGGER.info("Successfully binding network interface " + networkInterface + ", port: " + playerMulticastPort);
}
catch(Exception e)
{
failBinds++;
UnityPlayerService.LOGGER.warn(e);
}
}
}
UnityPlayerService.LOGGER.info("Port status: " + succBinds + " vs " + failBinds);
myExecutorService.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
int size = myPlayers.size();
for(Iterator<Map.Entry<UnityPlayer, UnityPlayer>> iterator = myPlayers.entrySet().iterator(); iterator.hasNext(); )
{
Map.Entry<UnityPlayer, UnityPlayer> next = iterator.next();
if(!next.getKey().isAvailable())
{
iterator.remove();
}
}
List<UnityPlayer> values = new ArrayList<UnityPlayer>(myPlayers.values());
if(size != values.size())
{
myUpdateListenerEventDispatcher.getMulticaster().update(values);
}
}
}, 2, 2, TimeUnit.SECONDS);
}
catch(Exception e)
{
Messages.showErrorDialog("Some problem with Unity debugger. Exception: " + ExceptionUtil.getThrowableText(e), "Consulo");
}
}
private boolean haveIp4Address(NetworkInterface networkInterface)
{
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while(inetAddresses.hasMoreElements())
{
InetAddress address = inetAddresses.nextElement();
if(address instanceof Inet4Address)
{
return true;
}
}
return false;
}
public void addUpdateListener(@NotNull UpdateListener updateListener)
{
myUpdateListenerEventDispatcher.addListener(updateListener);
}
public void removeUpdateListener(@NotNull UpdateListener updateListener)
{
myUpdateListenerEventDispatcher.removeListener(updateListener);
}
@Override
public void disposeComponent()
{
myExecutorService.shutdown();
for(UnityUdpThread thread : myThreads)
{
thread.dispose();
}
}
@NotNull
@Override
public String getComponentName()
{
return "PlayerListenerService";
}
@NotNull
public Collection<UnityPlayer> getPlayers()
{
return myPlayers.values();
}
public void addPlayer(@NotNull UnityPlayer player)
{
UnityPlayer otherPlayer = myPlayers.get(player);
if(otherPlayer != null)
{
otherPlayer.update();
}
else
{
myPlayers.put(player, player);
myUpdateListenerEventDispatcher.getMulticaster().update(new ArrayList<UnityPlayer>(myPlayers.values()));
}
}
}