org.spout.engine.SpoutClient.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.engine.SpoutClient.java

Source

/*
 * This file is part of Spout.
 *
 * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
 * Spout is licensed under the Spout License Version 1.
 *
 * Spout is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * In addition, 180 days after any changes are published, you can use the
 * software, incorporating those changes, under the terms of the MIT license,
 * as described in the Spout License Version 1.
 *
 * Spout is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License,
 * the MIT license and the Spout License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://spout.in/licensev1> for the full license, including
 * the MIT license.
 */
package org.spout.engine;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;

import org.spout.api.Client;
import org.spout.api.Platform;
import org.spout.api.audio.SoundManager;
import org.spout.api.command.annotated.AnnotatedCommandExecutorFactory;
import org.spout.api.component.entity.PlayerNetworkComponent;
import org.spout.api.datatable.SerializableMap;
import org.spout.api.entity.Entity;
import org.spout.api.event.engine.EngineStartEvent;
import org.spout.api.event.engine.EngineStopEvent;
import org.spout.api.geo.World;
import org.spout.api.geo.discrete.Point;
import org.spout.api.geo.discrete.Transform;
import org.spout.api.protocol.CommonChannelInitializer;
import org.spout.api.protocol.CommonHandler;
import org.spout.api.protocol.PortBinding;
import org.spout.api.protocol.Protocol;
import org.spout.api.protocol.Session;
import org.spout.api.render.RenderMode;
import org.spout.api.resource.FileSystem;

import org.spout.engine.audio.AudioConfiguration;
import org.spout.engine.audio.SpoutSoundManager;
import org.spout.engine.command.InputCommands;
import org.spout.engine.command.RendererCommands;
import org.spout.engine.entity.SpoutClientPlayer;
import org.spout.engine.filesystem.ClientFileSystem;
import org.spout.engine.gui.SpoutScreenStack;
import org.spout.engine.input.SpoutInputManager;
import org.spout.engine.protocol.PortBindingImpl;
import org.spout.engine.protocol.SpoutClientSession;
import org.spout.engine.world.SpoutClientWorld;
import org.spout.engine.world.SpoutRegion;
import org.spout.math.vector.Vector2;

public class SpoutClient extends SpoutEngine implements Client {
    private final AtomicReference<SpoutClientPlayer> player = new AtomicReference<>();
    private final AtomicReference<SpoutClientWorld> world = new AtomicReference<>();
    private final Bootstrap bootstrap = new Bootstrap();
    private final ClientFileSystem filesystem = new ClientFileSystem();
    private final SessionTask sessionTask = new SessionTask();
    // Handle stopping
    private volatile boolean rendering = true;
    private boolean ccoverride = false;
    private String stopMessage = null;
    private SpoutRenderer renderer;
    private SoundManager soundManager;
    private SpoutInputManager inputManager;

    public SpoutClient() {
        logFile = "client-log-%D.txt";
    }

    @Override
    public void init(SpoutApplication args) {
        boolean inJar = false;

        try {
            CodeSource cs = SpoutClient.class.getProtectionDomain().getCodeSource();
            inJar = cs.getLocation().toURI().getPath().endsWith(".jar");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        if (inJar || args.path != null) {
            unpackNatives(args.path);
        }

        bootstrap.handler(new CommonChannelInitializer()).channel(NioSocketChannel.class)
                .group(new NioEventLoopGroup());

        super.init(args);

        this.ccoverride = args.ccoverride;

        inputManager = new SpoutInputManager();

        // Initialize sound system
        soundManager = new SpoutSoundManager();
        soundManager.init();

        // Configure sound system
        AudioConfiguration audioConfig = new AudioConfiguration();
        audioConfig.load();
        soundManager.setGain(AudioConfiguration.SOUND_VOLUME.getFloat());
        soundManager.setMusicGain(AudioConfiguration.MUSIC_VOLUME.getFloat());
    }

    @Override
    public void start() {
        // Completely blank world to allow the player to start in
        this.world.getAndSet(new SpoutClientWorld("NullWorld", this, UUID.randomUUID()));
        if (!connnect()) {
            return;
        }

        super.start();

        // Register commands
        AnnotatedCommandExecutorFactory.create(new InputCommands(this));
        AnnotatedCommandExecutorFactory.create(new RendererCommands(this));

        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();

        this.renderer = getScheduler()
                .startRenderThread(new Vector2(dim.getWidth() * 0.75f, dim.getHeight() * 0.75f), ccoverride, null);
        getScheduler().startMeshThread();
        getScheduler().startGuiThread();

        // TODO: Maybe a better way of alerting plugins the client is done?
        if (EngineStartEvent.getHandlerList().getRegisteredListeners().length != 0) {
            getEventManager().callEvent(new EngineStartEvent());
        }

        inputManager.onClientStart();
        filesystem.postStartup();

        // Send handshake message first
        SpoutClientSession get = (SpoutClientSession) player.get().getNetwork().getSession();
        get.send(Session.SendType.FORCE, get.getProtocol().getIntroductionMessage(getPlayer().getName(),
                (InetSocketAddress) get.getChannel().remoteAddress()));
    }

    private boolean connnect() {
        // Connect to server to establish session
        Protocol protocol = null;
        if (getArguments().protocol != null) {
            protocol = Protocol.getProtocol(getArguments().protocol);
        }
        if (protocol == null) {
            protocol = Protocol.getProtocol("Spout");
        }
        String address;
        if (getArguments().server == null) {
            address = "localhost";
        } else {
            address = getArguments().server;
        }
        int port = getArguments().port != -1 ? getArguments().port : protocol.getDefaultPort();
        PortBindingImpl binding = new PortBindingImpl(protocol, new InetSocketAddress(address, port));
        ChannelFuture connect = bootstrap.connect(binding.getAddress());
        try {
            connect.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
            getLogger().log(Level.SEVERE, "Connection took too long! Cancelling connect and stopping engine!");
            stop();
            return false;
        }

        Channel channel = connect.channel();
        if (!connect.isSuccess()) {
            getLogger().log(Level.SEVERE, "Could not connect to " + binding, connect.cause());
            return false;
        }

        getLogger().log(Level.INFO,
                "Connected to " + address + ":" + port + " with protocol " + protocol.getName());
        CommonHandler handler = channel.pipeline().get(CommonHandler.class);
        SpoutClientSession session = new SpoutClientSession(this, channel, protocol);
        handler.setSession(session);

        Class<? extends PlayerNetworkComponent> network = session.getProtocol().getClientNetworkComponent(session);
        final SpoutClientPlayer p = new SpoutClientPlayer(this, network, "Spouty",
                new Transform().setPosition(new Point(getWorld(), 1, 200, 1)));
        session.setPlayer(p);
        p.getNetwork().setSession(session);
        session.getProtocol().initializeClientSession(session);
        player.set(p);
        return true;
    }

    @Override
    protected Runnable getSessionTask() {
        return sessionTask;
    }

    @Override
    public void startTickRun(int stage, long delta) {
    }

    private class SessionTask implements Runnable {
        @Override
        public void run() {
            ((SpoutClientSession) player.get().getNetwork().getSession()).pulse();
        }
    }

    @Override
    public List<String> getAllPlayers() {
        return Arrays.asList(getPlayer().getName());
    }

    @Override
    public SpoutClientPlayer getPlayer() {
        return player.get();
    }

    @Override
    public Platform getPlatform() {
        return Platform.CLIENT;
    }

    @Override
    public RenderMode getRenderMode() {
        return getArguments().renderMode;
    }

    @Override
    public String getName() {
        return "Spout Client";
    }

    @Override
    public SoundManager getSoundManager() {
        return soundManager;
    }

    @Override
    public SpoutInputManager getInputManager() {
        return this.inputManager;
    }

    @Override
    public PortBinding getAddress() {
        return ((SpoutClientSession) player.get().getNetwork().getSession()).getActiveAddress();
    }

    @Override
    public boolean stop(String message) {
        if (!super.stop(message, false)) {
            return false;
        }

        if (!player.get().getNetwork().getSession().isDisconnected()) {
            player.get().getNetwork().getSession().disconnect("Spout shutting down");
        }

        // De-init OpenAL
        soundManager.destroy();
        soundManager = null;

        rendering = false;
        stopMessage = message;
        Runnable finalTask = new Runnable() {
            @Override
            public void run() {
                EngineStopEvent stopEvent = new EngineStopEvent(stopMessage);
                getEventManager().callEvent(stopEvent);
                stopMessage = stopEvent.getMessage();
                System.out.println(stopMessage);

                bootstrap.group().shutdownGracefully();
                boundProtocols.clear();
            }
        };
        getScheduler().submitFinalTask(finalTask, true);
        getScheduler().stop();
        return true;
    }

    public boolean isRendering() {
        return rendering;
    }

    @Override
    public SpoutClientWorld getWorld(String name, boolean exact) {
        SpoutClientWorld world = this.world.get();
        if (world == null) {
            return null;
        }

        if ((exact && world.getName().equals(name)) || world.getName().startsWith(name)) {
            return world;
        } else {
            return null;
        }
    }

    @Override
    public SpoutClientWorld getDefaultWorld() {
        return world.get();
    }

    public SpoutClientWorld worldChanged(String name, UUID uuid, byte[] data) {
        SpoutClientWorld world = new SpoutClientWorld(name, this, uuid);
        // Load in datatable
        SerializableMap map = world.getData();
        try {
            map.deserialize(data);
        } catch (IOException e) {
            throw new RuntimeException("Unable to deserialize data", e);
        }

        SpoutClientWorld oldWorld = this.world.getAndSet(world);
        if (oldWorld != null) {
            if (oldWorld.getName().equals("NullWorld")) {
                ((SpoutRegion) player.get().getRegion()).getEntityManager().addEntity(player.get());
            } else if (!scheduler.removeAsyncManager(oldWorld)) {
                throw new IllegalStateException("Unable to remove old world from scheduler");
            }
            world.addLocalPlayer(player.get());
        }
        if (!scheduler.addAsyncManager(world)) {
            this.world.compareAndSet(world, null);
            throw new IllegalStateException("Unable to add new world to the scheduler");
        }
        return world;
    }

    @Override
    public FileSystem getFileSystem() {
        return filesystem;
    }

    @Override
    public Entity getEntity(UUID uid) {
        return world.get().getEntity(uid);
    }

    @Override
    public Entity getEntity(int id) {
        return world.get().getEntity(id);
    }

    @Override
    public SpoutClientWorld getWorld() {
        return world.get();
    }

    @Override
    public Collection<World> getWorlds() {
        return Collections.singleton((World) world.get());
    }

    private void unpackNatives(String path) {
        String natives;
        String osPath;
        if (SystemUtils.IS_OS_WINDOWS) {
            natives = "windows.txt";
            osPath = "windows/";
        } else if (SystemUtils.IS_OS_MAC) {
            natives = "osx.txt";
            osPath = "mac/";
        } else if (SystemUtils.IS_OS_LINUX) {
            natives = "linux.txt";
            osPath = "linux/";
        } else {
            getLogger().severe("Error loading natives of operating system type: " + SystemUtils.OS_NAME);
            return;
        }

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(SpoutClient.class.getResourceAsStream("/natives/" + natives)));
        String str;
        List<String> files = new ArrayList<>();
        try {
            while ((str = reader.readLine()) != null) {
                files.add(str);
            }
        } catch (IOException e) {
            getLogger().log(Level.SEVERE, "Error getting native files to copy", e);
        }

        File cacheDir = new File(path == null ? System.getProperty("user.dir") : path,
                "natives" + File.separator + osPath);
        cacheDir.mkdirs();
        for (String f : files) {
            File target = new File(cacheDir, f);
            if (!target.exists()) {
                try {
                    FileUtils.copyInputStreamToFile(SpoutClient.class.getResourceAsStream("/" + f), target);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String nativePath = cacheDir.getAbsolutePath();
        System.setProperty("org.lwjgl.librarypath", nativePath);
        System.setProperty("net.java.games.input.librarypath", nativePath);
    }

    @Override
    public Vector2 getResolution() {
        return renderer.getResolution();
    }

    @Override
    public float getAspectRatio() {
        return renderer.getAspectRatio();
    }

    @Override
    public SpoutScreenStack getScreenStack() {
        return renderer.getScreenStack();
    }

    public SpoutRenderer getRenderer() {
        return renderer;
    }
}