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 bool isDisconnecting = false; 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!ushort("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("connect", &connectCommand); 94 95 connection.registerPacketHandler!PacketMapPacket(&handlePacketMapPacket); 96 } 97 98 void handleGameStartEvent(ref GameStartEvent event) 99 { 100 ConnectionSettings settings = {null, 1, 2, 0, 0}; 101 102 connection.start(settings); 103 static if (ENABLE_RLE_PACKET_COMPRESSION) 104 enet_host_compress_with_range_coder(connection.host); 105 connect(serverIpOpt.get!string, serverPortOpt.get!ushort); 106 } 107 108 void connect(string ip, ushort port) 109 { 110 infof("Connecting to %s:%s", ip, port); 111 if (connection.isConnecting) 112 connection.disconnect(); 113 connection.connect(ip, port); 114 } 115 116 void connectCommand(CommandParams params) 117 { 118 short port = serverPortOpt.get!ushort; 119 string serverIp = serverIpOpt.get!string; 120 getopt(params.args, 121 "ip", &serverIp, 122 "port", &port); 123 connect(serverIp, port); 124 } 125 126 void onPreUpdateEvent(ref PreUpdateEvent event) 127 { 128 connection.update(); 129 } 130 131 void onPostUpdateEvent(ref PostUpdateEvent event) 132 { 133 connection.flush(); 134 135 if (event.frame % 30 == 0) { 136 enum maxLen = 120; 137 with (connection.host) { 138 dbg.logVar("Recv (B)", cast(float)totalReceivedData, maxLen); 139 dbg.logVar("Send (B)", cast(float)totalSentData, maxLen); 140 } 141 connection.host.totalReceivedData = 0; 142 connection.host.totalSentData = 0; 143 } 144 } 145 146 void onConnect(ref ENetEvent event) { 147 } 148 149 void onDisconnect(ref ENetEvent event) { 150 event.peer.data = null; 151 evDispatcher.postEvent(ThisClientDisconnectedEvent(event.data)); 152 } 153 154 void handleThisClientConnected(ref ThisClientConnectedEvent event) 155 { 156 infof("Connection to %s:%s established", serverIpOpt.get!string, serverPortOpt.get!ushort); 157 } 158 159 void handleThisClientDisconnected(ref ThisClientDisconnectedEvent event) 160 { 161 infof("disconnected with data %s", event.data); 162 isDisconnecting = false; 163 } 164 165 void handlePacketMapPacket(ubyte[] packetData, ClientId clientId) 166 { 167 auto packetMap = unpackPacket!PacketMapPacket(packetData); 168 connection.setPacketMap(packetMap.packetNames); 169 connection.printPacketMap(); 170 } 171 172 void onGameStopEvent(ref GameStopEvent gameStopEvent) 173 { 174 if (!connection.isConnected) return; 175 176 connection.disconnect(); 177 178 isDisconnecting = true; 179 MonoTime start = MonoTime.currTime; 180 181 size_t counter; 182 while (connection.isConnected && counter < 100) 183 { 184 connection.update(); 185 Thread.sleep(1.msecs); 186 ++counter; 187 } 188 189 isDisconnecting = false; 190 Duration disconTime = MonoTime.currTime - start; 191 infof("disconnected in %s seconds", 192 disconTime.total!"seconds" + 193 0.001 * disconTime.total!"msecs" + 194 0.000_001 * disconTime.total!"usecs"); 195 } 196 } 197 198 final class NetServerPlugin : IPlugin 199 { 200 private: 201 ConfigOption portOpt; 202 ConfigOption maxPlayers; 203 EventDispatcherPlugin evDispatcher; 204 string[][string] idMaps; 205 206 public: 207 mixin NetCommon; 208 209 BaseServer connection; 210 alias connection this; 211 212 override void registerResources(IResourceManagerRegistry resmanRegistry) 213 { 214 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 215 portOpt = config.registerOption!ushort("port", 1234); 216 maxPlayers = config.registerOption!uint("max_players", 32); 217 } 218 219 this() 220 { 221 connection = new class BaseServer{}; 222 } 223 224 override void init(IPluginManager pluginman) 225 { 226 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 227 evDispatcher.subscribeToEvent(&handleGameStartEvent); 228 evDispatcher.subscribeToEvent(&onPreUpdateEvent); 229 evDispatcher.subscribeToEvent(&onPostUpdateEvent); 230 evDispatcher.subscribeToEvent(&handleGameStopEvent); 231 } 232 233 override void postInit() 234 { 235 //connection.shufflePackets(); 236 connection.printPacketMap(); 237 } 238 239 void handleGameStartEvent(ref GameStartEvent event) 240 { 241 ConnectionSettings settings = {null, maxPlayers.get!uint, 2, 0, 0}; 242 connection.start(settings, ENET_HOST_ANY, portOpt.get!ushort); 243 static if (ENABLE_RLE_PACKET_COMPRESSION) 244 enet_host_compress_with_range_coder(connection.host); 245 } 246 247 void onConnect(ref ENetEvent event) { 248 auto clientId = connection.clientStorage.addClient(event.peer); 249 event.peer.data = cast(void*)clientId; 250 251 connection.sendTo(clientId, PacketMapPacket(connection.packetNames)); 252 evDispatcher.postEvent(ClientConnectedEvent(clientId)); 253 connection.sendTo(clientId, GameStartPacket()); 254 } 255 256 void onDisconnect(ref ENetEvent event) { 257 ClientId clientId = cast(ClientId)event.peer.data; 258 event.peer.data = null; 259 connection.clientStorage.removeClient(clientId); 260 evDispatcher.postEvent(ClientDisconnectedEvent(clientId)); 261 } 262 263 void onPreUpdateEvent(ref PreUpdateEvent event) 264 { 265 connection.update(); 266 } 267 268 void onPostUpdateEvent(ref PostUpdateEvent event) 269 { 270 connection.flush(); 271 } 272 273 void handleGameStopEvent(ref GameStopEvent event) 274 { 275 connection.sendToAll(MessagePacket(0, "Stopping server")); 276 connection.disconnectAll(); 277 278 bool isDisconnecting = true; 279 MonoTime start = MonoTime.currTime; 280 281 size_t counter; 282 while (connection.clientStorage.length && counter < 100) 283 { 284 connection.update(); 285 Thread.sleep(1.msecs); 286 ++counter; 287 } 288 connection.stop(); 289 290 isDisconnecting = false; 291 //Duration disconTime = MonoTime.currTime - start; 292 //infof("disconnected in %s seconds", 293 // disconTime.total!"seconds" + 294 // 0.001 * disconTime.total!"msecs" + 295 // 0.000_001 * disconTime.total!"usecs"); 296 } 297 }