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 }