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