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