001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server; 019 020import io.netty.bootstrap.ServerBootstrap; 021import io.netty.channel.ChannelOption; 022import io.netty.channel.EventLoopGroup; 023import io.netty.channel.nio.NioEventLoopGroup; 024import io.netty.channel.socket.nio.NioServerSocketChannel; 025import joptsimple.OptionException; 026import joptsimple.OptionParser; 027import joptsimple.OptionSet; 028import joptsimple.OptionSpec; 029import net.tridentsdk.Defaults; 030import net.tridentsdk.Trident; 031import net.tridentsdk.config.Config; 032import net.tridentsdk.docs.Policy; 033import net.tridentsdk.plugin.Plugins; 034import net.tridentsdk.registry.Implementation; 035import net.tridentsdk.registry.Registered; 036import net.tridentsdk.server.command.ServerCommandRegistrar; 037import net.tridentsdk.server.concurrent.TickSync; 038import net.tridentsdk.server.netty.ClientChannelInitializer; 039import net.tridentsdk.server.service.Statuses; 040import net.tridentsdk.server.service.TridentImpl; 041import net.tridentsdk.server.world.TridentWorld; 042import net.tridentsdk.server.world.TridentWorldLoader; 043import net.tridentsdk.util.TridentLogger; 044import net.tridentsdk.world.World; 045import org.apache.log4j.Level; 046 047import javax.annotation.concurrent.ThreadSafe; 048import java.io.File; 049import java.io.InputStream; 050import java.net.InetSocketAddress; 051import java.nio.file.Files; 052import java.util.NoSuchElementException; 053import java.util.Scanner; 054 055import static com.google.common.collect.Lists.newArrayList; 056 057/** 058 * Server class that starts the connection listener. 059 * <p/> 060 * <p>Despite the fact that this class is under protected access, 061 * it is documented anyways because of its significance in the server</p> 062 * 063 * @author The TridentSDK Team 064 */ 065@ThreadSafe 066public final class TridentStart { 067 private static volatile EventLoopGroup bossGroup; 068 private static volatile EventLoopGroup workerGroup; 069 070 private TridentStart() { 071 } // Do not initialize 072 073 /** 074 * Starts the server up when the jarfile is run 075 * 076 * @param args the command line arguments 077 */ 078 @Policy("Do not throw exceptions in this method") 079 public static void main(String... args) throws Exception { 080 OptionParser parser = new OptionParser(); 081 parser.acceptsAll(newArrayList("h", "help"), "Show this help dialog.").forHelp(); 082 parser.accepts("d", "Prints debug level logging output"); 083 parser.acceptsAll(newArrayList("log-append"), "Whether to append to the log file") 084 .withRequiredArg() 085 .ofType(Boolean.class) 086 .defaultsTo(true) 087 .describedAs("Log append"); 088 OptionSpec<File> properties = parser.acceptsAll(newArrayList("properties"), 089 "The location for the properties file") 090 .withRequiredArg() 091 .ofType(File.class) 092 .defaultsTo(new File("server.json")) 093 .describedAs("Properties file"); 094 095 OptionSet options; 096 try { 097 options = parser.parse(args); 098 } catch (OptionException ex) { 099 TridentLogger.get().error(ex); 100 return; 101 } 102 103 boolean d = options.has("d"); 104 TridentLogger.init(d ? Level.DEBUG : Level.INFO); 105 TickSync.DEBUG = d; 106 107 TridentLogger.get().log("Open source software by TridentSDK - https://github.com/TridentSDK"); 108 TridentLogger.get().log("Starting Trident server"); 109 110 TridentLogger.get().log("Looking for server files..."); 111 File f = properties.value(options); 112 if (!f.exists()) { 113 TridentLogger.get().warn("Server properties not found, creating one for you..."); 114 InputStream link = TridentServer.class.getResourceAsStream("/server.json"); 115 Files.copy(link, f.getAbsoluteFile().toPath()); 116 } 117 118 TridentLogger.get().log("Initializing the API implementations"); 119 Implementation implementation = new TridentImpl(); 120 Registered.setProvider(implementation); 121 TridentLogger.get().success("Loaded API implementations."); 122 123 ((Statuses) Registered.statuses()).loadAll(); 124 TridentLogger.get().success("Loaded the server files"); 125 126 TridentLogger.get().log("Starting server process..."); 127 init(new Config(f)); 128 } 129 130 /** 131 * Initializes the server with the configuration file 132 * 133 * @param config the configuration to use for option lookup 134 */ 135 private static void init(final Config config) throws InterruptedException { 136 bossGroup = new NioEventLoopGroup(4, Defaults.ERROR_HANDLED); 137 workerGroup = new NioEventLoopGroup(4, Defaults.ERROR_HANDLED); 138 139 try { 140 TridentLogger.get().log("Creating server..."); 141 TridentServer.createServer(config); 142 TridentLogger.get().success("Server created."); 143 144 // Required before loading worlds to find all class files in case the plugin has a world generator 145 TridentLogger.get().log("Loading plugins..."); 146 File fi = new File(System.getProperty("user.dir") + File.separator + "plugins"); 147 if (!fi.exists()) 148 fi.mkdir(); 149 150 for (File file : new File(System.getProperty("user.dir") + File.separator + "plugins") 151 .listFiles((dir, name) -> name.endsWith(".jar"))) 152 Registered.plugins().load(file); 153 TridentLogger.get().success("Loaded plugins."); 154 155 TridentWorldLoader.loadAll(); 156 TridentServer.WORLD = (TridentWorld) Registered.worlds().get("world"); 157 158 if (TridentServer.WORLD == null) { 159 World world = TridentServer.instance().rootWorldLoader.createWorld("world"); 160 TridentServer.WORLD = (TridentWorld) world; 161 } 162 163 TridentLogger.get().log("Setting server commands..."); 164 ServerCommandRegistrar.registerAll(); 165 TridentLogger.get().success("Server commands set."); 166 167 TridentLogger.get().log("Enabling plugins..."); 168 Plugins handler = Registered.plugins(); 169 handler.forEach(handler::enable); 170 TridentLogger.get().success("Enabled plugins."); 171 172 ////////////////////////////////// NETTY SETUP ////////////////////////////////////////// 173 174 TridentLogger.get().log("Creating server connections..."); 175 String ip = config.getString("address", Defaults.ADDRESS); 176 int port = config.getInt("port", Defaults.PORT); 177 178 // FIXME double shutdown for no reason... 179 // Runtime.getRuntime().addShutdownHook(new Thread(() -> TridentServer.instance().shutdown())); 180 181 TridentLogger.get().log("Binding socket to server address, using address:port " + ip + ":" + port); 182 183 new ServerBootstrap().group(bossGroup, workerGroup) 184 .channel(NioServerSocketChannel.class) 185 .childHandler(new ClientChannelInitializer()) 186 .option(ChannelOption.TCP_NODELAY, true) 187 .bind(new InetSocketAddress(ip, port)) 188 .sync(); 189 190 TridentLogger.get().success("Server started."); 191 192 /////////////////////////// Console command handling //////////////////////////////////// 193 Thread thread = new Thread(() -> { 194 Scanner scanner = new Scanner(System.in); 195 196 while (true) { 197 String command = scanner.nextLine(); 198 System.out.print("$ "); 199 200 Trident.console().invokeCommand(command); 201 } 202 }); 203 thread.setName("Trident - Console"); 204 thread.setDaemon(true); 205 thread.start(); 206 } catch (InterruptedException e) { 207 // This exception is caught if server is closed. 208 } catch (NoSuchElementException e) { 209 // For some reason, this is thrown when the server is quit 210 } catch (Exception e) { 211 TridentLogger.get().error("Server closed, error occurred"); 212 TridentLogger.get().error(e); 213 Trident.shutdown(); 214 } 215 } 216 217 /** 218 * Shuts down the backed event loops 219 */ 220 public static void close() { 221 workerGroup.shutdownGracefully().awaitUninterruptibly(); 222 bossGroup.shutdownGracefully().awaitUninterruptibly(); 223 } 224}