1 /** 2 Copyright: Copyright (c) 2015-2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module voxelman.net.plugin; 8 9 import std.experimental.logger; 10 import derelict.enet.enet; 11 import std.datetime : MonoTime, Duration, usecs, dur; 12 import core.thread; 13 14 import pluginlib; 15 public import netlib; 16 import derelict.enet.enet; 17 18 import voxelman.core.config; 19 import voxelman.net.events; 20 import voxelman.core.events; 21 import voxelman.core.packets; 22 import voxelman.net.packets; 23 24 import voxelman.eventdispatcher.plugin; 25 import voxelman.command.plugin; 26 import voxelman.dbg.plugin; 27 import voxelman.config.configmanager; 28 29 shared static this() 30 { 31 pluginRegistry.regClientPlugin(new NetClientPlugin); 32 pluginRegistry.regServerPlugin(new NetServerPlugin); 33 } 34 35 mixin template NetCommon() 36 { 37 mixin IdAndSemverFrom!(voxelman.net.plugininfo); 38 private EventDispatcherPlugin evDispatcher; 39 40 override void preInit() { 41 import voxelman.utils.libloader; 42 loadEnet([getLibName(BUILD_TO_ROOT_PATH, "enet")]); 43 44 connection.connectHandler = &onConnect; 45 connection.disconnectHandler = &onDisconnect; 46 voxelman.net.packets.registerPackets(connection); 47 voxelman.core.packets.registerPackets(connection); 48 } 49 50 override void init(IPluginManager pluginman) { 51 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 52 } 53 } 54 55 final class NetClientPlugin : IPlugin 56 { 57 CommandPluginClient commandPlugin; 58 Debugger dbg; 59 60 ConfigOption serverIpOpt; 61 ConfigOption serverPortOpt; 62 63 mixin NetCommon; 64 65 BaseClient connection; 66 alias connection this; 67 68 this() { 69 connection = new class BaseClient{}; 70 } 71 72 override void registerResources(IResourceManagerRegistry resmanRegistry) 73 { 74 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 75 dbg = resmanRegistry.getResourceManager!Debugger; 76 77 serverIpOpt = config.registerOption!string("ip", "127.0.0.1"); 78 serverPortOpt = config.registerOption!int("port", 1234); 79 } 80 81 override void init(IPluginManager pluginman) 82 { 83 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 84 evDispatcher.subscribeToEvent(&handleGameStartEvent); 85 evDispatcher.subscribeToEvent(&onPreUpdateEvent); 86 evDispatcher.subscribeToEvent(&onPostUpdateEvent); 87 evDispatcher.subscribeToEvent(&onGameStopEvent); 88 evDispatcher.subscribeToEvent(&handleThisClientConnected); 89 evDispatcher.subscribeToEvent(&handleThisClientDisconnected); 90 91 commandPlugin = pluginman.getPlugin!CommandPluginClient; 92 commandPlugin.registerCommand("connect", &connectCommand); 93 94 connection.registerPacketHandler!PacketMapPacket(&handlePacketMapPacket); 95 } 96 97 void handleGameStartEvent(ref GameStartEvent event) 98 { 99 ConnectionSettings settings = {null, 1, 2, 0, 0}; 100 101 connection.start(settings); 102 static if (ENABLE_RLE_PACKET_COMPRESSION) 103 enet_host_compress_with_range_coder(connection.host); 104 connect(serverIpOpt.get!string, serverPortOpt.get!ushort); 105 } 106 107 void connect(string ip, ushort port) 108 { 109 infof("Connecting to %s:%s", ip, port); 110 connection.connect(ip, port); 111 } 112 113 void connectCommand(CommandParams params) 114 { 115 short port = serverPortOpt.get!ushort; 116 string serverIp = serverIpOpt.get!string; 117 getopt(params.args, 118 "ip", &serverIp, 119 "port", &port); 120 connect(serverIp, port); 121 } 122 123 void onPreUpdateEvent(ref PreUpdateEvent event) 124 { 125 connection.update(); 126 } 127 128 void onPostUpdateEvent(ref PostUpdateEvent event) 129 { 130 connection.flush(); 131 132 if (event.frame % 30 == 0) { 133 enum maxLen = 120; 134 with (connection.host) { 135 dbg.logVar("Recv (B)", cast(float)totalReceivedData, maxLen); 136 dbg.logVar("Send (B)", cast(float)totalSentData, maxLen); 137 } 138 connection.host.totalReceivedData = 0; 139 connection.host.totalSentData = 0; 140 } 141 } 142 143 void onConnect(ref ENetEvent event) {} 144 145 void onDisconnect(ref ENetEvent event) { 146 event.peer.data = null; 147 evDispatcher.postEvent(ThisClientDisconnectedEvent(event.data)); 148 } 149 150 void handleThisClientConnected(ref ThisClientConnectedEvent event) 151 { 152 infof("Connection to %s:%s established", serverIpOpt.get!string, serverPortOpt.get!ushort); 153 } 154 155 void handleThisClientDisconnected(ref ThisClientDisconnectedEvent event) 156 { 157 tracef("disconnected with data %s", event.data); 158 } 159 160 void handlePacketMapPacket(ubyte[] packetData, ClientId clientId) 161 { 162 auto packetMap = unpackPacket!PacketMapPacket(packetData); 163 connection.setPacketMap(packetMap.packetNames); 164 connection.printPacketMap(); 165 } 166 167 void onGameStopEvent(ref GameStopEvent gameStopEvent) 168 { 169 if (!connection.isConnected) return; 170 171 connection.disconnect(); 172 173 MonoTime start = MonoTime.currTime; 174 175 size_t counter; 176 while (connection.isConnected && counter < 1000) 177 { 178 connection.update(); 179 Thread.sleep(5.msecs); 180 ++counter; 181 } 182 183 Duration disconTime = MonoTime.currTime - start; 184 infof("disconnected in %s msecs", disconTime.total!"msecs"); 185 } 186 } 187 188 final class NetServerPlugin : IPlugin 189 { 190 private: 191 ConfigOption portOpt; 192 ConfigOption maxPlayers; 193 EventDispatcherPlugin evDispatcher; 194 string[][string] idMaps; 195 196 public: 197 mixin NetCommon; 198 199 BaseServer connection; 200 alias connection this; 201 202 override void registerResources(IResourceManagerRegistry resmanRegistry) 203 { 204 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 205 portOpt = config.registerOption!int("port", 1234); 206 maxPlayers = config.registerOption!int("max_players", 32); 207 } 208 209 this() 210 { 211 connection = new class BaseServer{}; 212 } 213 214 override void init(IPluginManager pluginman) 215 { 216 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 217 evDispatcher.subscribeToEvent(&handleGameStartEvent); 218 evDispatcher.subscribeToEvent(&onPreUpdateEvent); 219 evDispatcher.subscribeToEvent(&onPostUpdateEvent); 220 evDispatcher.subscribeToEvent(&handleGameStopEvent); 221 } 222 223 override void postInit() 224 { 225 //connection.shufflePackets(); 226 connection.printPacketMap(); 227 } 228 229 void handleGameStartEvent(ref GameStartEvent event) 230 { 231 ConnectionSettings settings = {null, maxPlayers.get!uint, 2, 0, 0}; 232 connection.start(settings, ENET_HOST_ANY, portOpt.get!ushort); 233 static if (ENABLE_RLE_PACKET_COMPRESSION) 234 enet_host_compress_with_range_coder(connection.host); 235 } 236 237 void onConnect(ref ENetEvent event) { 238 auto clientId = connection.clientStorage.addClient(event.peer); 239 event.peer.data = cast(void*)clientId; 240 241 connection.sendTo(clientId, PacketMapPacket(connection.packetNames)); 242 evDispatcher.postEvent(ClientConnectedEvent(clientId)); 243 connection.sendTo(clientId, GameStartPacket()); 244 } 245 246 void onDisconnect(ref ENetEvent event) { 247 ClientId clientId = cast(ClientId)event.peer.data; 248 event.peer.data = null; 249 connection.clientStorage.removeClient(clientId); 250 evDispatcher.postEvent(ClientDisconnectedEvent(clientId)); 251 } 252 253 void onPreUpdateEvent(ref PreUpdateEvent event) 254 { 255 connection.update(); 256 } 257 258 void onPostUpdateEvent(ref PostUpdateEvent event) 259 { 260 connection.flush(); 261 } 262 263 void handleGameStopEvent(ref GameStopEvent event) 264 { 265 connection.sendToAll(MessagePacket(0, "Stopping server")); 266 connection.disconnectAll(); 267 268 bool isDisconnecting = true; 269 MonoTime start = MonoTime.currTime; 270 271 size_t counter; 272 while (connection.clientStorage.length && counter < 100) 273 { 274 connection.update(); 275 Thread.sleep(1.msecs); 276 ++counter; 277 } 278 connection.stop(); 279 280 isDisconnecting = false; 281 //Duration disconTime = MonoTime.currTime - start; 282 //infof("disconnected in %s seconds", 283 // disconTime.total!"seconds" + 284 // 0.001 * disconTime.total!"msecs" + 285 // 0.000_001 * disconTime.total!"usecs"); 286 } 287 }