1 /** 2 Copyright: Copyright (c) 2015-2018 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 voxelman.log; 10 import derelict.enet.enet; 11 import core.time : 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 import voxelman.session; 29 30 31 mixin template NetCommon() 32 { 33 mixin IdAndSemverFrom!"voxelman.net.plugininfo"; 34 private EventDispatcherPlugin evDispatcher; 35 36 override void preInit() { 37 loadEnet(); 38 39 sniffer.disallowedPakets.put("TelemetryPacket"); 40 sniffer.disallowedPakets.put("ComponentSyncStartPacket"); 41 sniffer.disallowedPakets.put("ComponentSyncEndPacket"); 42 sniffer.disallowedPakets.put("ChunkDataPacket"); 43 sniffer.disallowedPakets.put("ClientPositionPacket"); 44 45 connection.connectHandler = &onConnect; 46 connection.disconnectHandler = &onDisconnect; 47 voxelman.net.packets.registerPackets(connection); 48 voxelman.core.packets.registerPackets(connection); 49 } 50 51 override void init(IPluginManager pluginman) { 52 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 53 } 54 } 55 56 final class NetClientPlugin : IPlugin 57 { 58 CommandPluginClient commandPlugin; 59 Debugger dbg; 60 61 ConfigOption serverIpOpt; 62 ConfigOption serverPortOpt; 63 64 mixin NetCommon; 65 66 BaseClient connection; 67 alias connection this; 68 69 this() { 70 connection = new class BaseClient{}; 71 } 72 73 override void registerResources(IResourceManagerRegistry resmanRegistry) 74 { 75 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 76 dbg = resmanRegistry.getResourceManager!Debugger; 77 78 serverIpOpt = config.registerOption!string("ip", "127.0.0.1"); 79 serverPortOpt = config.registerOption!int("port", 1234); 80 } 81 82 override void init(IPluginManager pluginman) 83 { 84 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 85 evDispatcher.subscribeToEvent(&handleGameStartEvent); 86 evDispatcher.subscribeToEvent(&onPreUpdateEvent); 87 evDispatcher.subscribeToEvent(&onPostUpdateEvent); 88 evDispatcher.subscribeToEvent(&onGameStopEvent); 89 evDispatcher.subscribeToEvent(&handleThisClientConnected); 90 evDispatcher.subscribeToEvent(&handleThisClientDisconnected); 91 92 commandPlugin = pluginman.getPlugin!CommandPluginClient; 93 commandPlugin.registerCommand(CommandInfo("connect", &connectCommand, ["[--ip=<ip_address>] [--port=<port_number>]"], "Connects to the specified server")); 94 95 connection.registerPacketHandler!PacketMapPacket(&handlePacketMapPacket); 96 connection.registerPacketHandler!MessagePacket(&handleMessagePacket); 97 } 98 99 void handleGameStartEvent(ref GameStartEvent event) 100 { 101 ConnectionSettings settings = {null, 1, 2, 0, 0}; 102 103 connection.start(settings); 104 static if (ENABLE_RLE_PACKET_COMPRESSION) 105 enet_host_compress_with_range_coder(connection.host); 106 connect(serverIpOpt.get!string, serverPortOpt.get!ushort); 107 } 108 109 void connect(string ip, ushort port) 110 { 111 infof("Connecting to %s:%s", ip, port); 112 connection.connect(ip, port); 113 } 114 115 void connectCommand(CommandParams params) 116 { 117 short port = serverPortOpt.get!ushort; 118 string serverIp = serverIpOpt.get!string; 119 getopt(params.args, 120 "ip", &serverIp, 121 "port", &port); 122 connect(serverIp, port); 123 } 124 125 void onPreUpdateEvent(ref PreUpdateEvent event) 126 { 127 connection.update(); 128 } 129 130 void onPostUpdateEvent(ref PostUpdateEvent event) 131 { 132 connection.flush(); 133 134 if (event.frame % 30 == 0) { 135 enum maxLen = 120; 136 with (connection.host) { 137 dbg.logVar("Recv (B)", cast(float)totalReceivedData, maxLen); 138 dbg.logVar("Send (B)", cast(float)totalSentData, maxLen); 139 } 140 connection.host.totalReceivedData = 0; 141 connection.host.totalSentData = 0; 142 } 143 } 144 145 void onConnect(ref ENetEvent event) {} 146 147 void onDisconnect(ref ENetEvent event) { 148 event.peer.data = null; 149 evDispatcher.postEvent(ThisClientDisconnectedEvent(event.data)); 150 } 151 152 void handleThisClientConnected(ref ThisClientConnectedEvent event) 153 { 154 infof("Connection to %s:%s established", serverIpOpt.get!string, serverPortOpt.get!ushort); 155 } 156 157 void handleThisClientDisconnected(ref ThisClientDisconnectedEvent event) 158 { 159 tracef("disconnected with data %s", event.data); 160 } 161 162 void handlePacketMapPacket(ubyte[] packetData) 163 { 164 auto packetMap = unpackPacket!PacketMapPacket(packetData); 165 connection.setPacketMap(packetMap.packetNames); 166 connection.printPacketMap(); 167 } 168 169 void handleMessagePacket(ubyte[] packetData) 170 { 171 auto packet = unpackPacket!MessagePacket(packetData); 172 173 if (packet.endpoint == MessageEndpoint.launcherConsole) 174 { 175 infof(packet.msg); 176 } 177 else 178 { 179 evDispatcher.postEvent(MessageEvent(packet)); 180 } 181 } 182 183 void onGameStopEvent(ref GameStopEvent gameStopEvent) 184 { 185 if (!connection.isConnected) return; 186 187 connection.disconnect(); 188 189 MonoTime start = MonoTime.currTime; 190 191 size_t counter; 192 while (connection.isConnected && counter < 1000) 193 { 194 connection.update(); 195 Thread.sleep(5.msecs); 196 ++counter; 197 } 198 199 Duration disconTime = MonoTime.currTime - start; 200 infof("disconnected in %s msecs", disconTime.total!"msecs"); 201 } 202 } 203 204 final class NetServerPlugin : IPlugin 205 { 206 private: 207 ConfigOption portOpt; 208 ConfigOption maxPlayers; 209 ClientManager clientMan; 210 EventDispatcherPlugin evDispatcher; 211 string[][string] idMaps; 212 213 public: 214 mixin NetCommon; 215 216 BaseServer connection; 217 alias connection this; 218 219 override void registerResources(IResourceManagerRegistry resmanRegistry) 220 { 221 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 222 portOpt = config.registerOption!int("port", 1234); 223 maxPlayers = config.registerOption!int("max_players", 32); 224 } 225 226 this() 227 { 228 connection = new class BaseServer{}; 229 } 230 231 override void init(IPluginManager pluginman) 232 { 233 clientMan = pluginman.getPlugin!ClientManager; 234 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 235 evDispatcher.subscribeToEvent(&handleGameStartEvent); 236 evDispatcher.subscribeToEvent(&onPreUpdateEvent); 237 evDispatcher.subscribeToEvent(&onPostUpdateEvent); 238 evDispatcher.subscribeToEvent(&handleGameStopEvent); 239 240 connection.registerPacketHandler!MessagePacket(&handleMessagePacket); 241 } 242 243 override void postInit() 244 { 245 //connection.shufflePackets(); 246 connection.printPacketMap(); 247 } 248 249 void handleGameStartEvent(ref GameStartEvent event) 250 { 251 ConnectionSettings settings = {null, maxPlayers.get!uint, 2, 0, 0}; 252 connection.start(settings, ENET_HOST_ANY, portOpt.get!ushort); 253 static if (ENABLE_RLE_PACKET_COMPRESSION) 254 enet_host_compress_with_range_coder(connection.host); 255 } 256 257 void onConnect(ref ENetEvent event) { 258 auto sessionId = connection.peerStorage.addClient(event.peer); 259 event.peer.data = cast(void*)sessionId; 260 261 connection.sendTo(sessionId, PacketMapPacket(connection.packetNames)); 262 evDispatcher.postEvent(ClientConnectedEvent(sessionId)); 263 connection.sendTo(sessionId, GameStartPacket()); 264 } 265 266 void onDisconnect(ref ENetEvent event) { 267 SessionId sessionId = SessionId(cast(size_t)event.peer.data); 268 event.peer.data = null; 269 connection.peerStorage.removeClient(sessionId); 270 evDispatcher.postEvent(ClientDisconnectedEvent(sessionId)); 271 } 272 273 void onPreUpdateEvent(ref PreUpdateEvent event) 274 { 275 connection.update(); 276 } 277 278 void onPostUpdateEvent(ref PostUpdateEvent event) 279 { 280 connection.flush(); 281 } 282 283 void handleMessagePacket(ubyte[] packetData, SessionId sessionId) 284 { 285 auto packet = unpackPacket!MessagePacket(packetData); 286 Session* session = clientMan.sessions[sessionId]; 287 packet.clientId = session.dbKey; 288 evDispatcher.postEvent(MessageEvent(packet, session.name)); 289 } 290 291 void handleGameStopEvent(ref GameStopEvent event) 292 { 293 connection.sendToAll(MessagePacket("Stopping server")); 294 connection.disconnectAll(); 295 296 bool isDisconnecting = true; 297 MonoTime start = MonoTime.currTime; 298 299 size_t counter; 300 while (connection.peerStorage.length && counter < 100) 301 { 302 connection.update(); 303 Thread.sleep(1.msecs); 304 ++counter; 305 } 306 connection.stop(); 307 308 isDisconnecting = false; 309 //Duration disconTime = MonoTime.currTime - start; 310 //infof("disconnected in %s seconds", 311 // disconTime.total!"seconds" + 312 // 0.001 * disconTime.total!"msecs" + 313 // 0.000_001 * disconTime.total!"usecs"); 314 } 315 }