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 }