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 }