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 module voxelman.client.plugin;
7 
8 import core.time;
9 import voxelman.log;
10 
11 import voxelman.math;
12 import dlib.math.matrix : Matrix4f;
13 import derelict.enet.enet;
14 import derelict.opengl3.gl3;
15 import derelict.imgui.imgui;
16 
17 import anchovy.fpshelper;
18 import anchovy.glerrors;
19 
20 import netlib;
21 import pluginlib;
22 
23 import voxelman.eventdispatcher.plugin;
24 import voxelman.graphics.plugin;
25 import voxelman.gui.plugin;
26 import voxelman.net.plugin;
27 import voxelman.command.plugin;
28 import voxelman.block.plugin;
29 import voxelman.world.clientworld;
30 import voxelman.dbg.plugin;
31 
32 import voxelman.core.config;
33 import voxelman.net.events;
34 import voxelman.core.events;
35 import voxelman.core.packets;
36 import voxelman.net.packets;
37 
38 import voxelman.config.configmanager;
39 import voxelman.input.keybindingmanager;
40 
41 import voxelman.world.mesh.chunkmesh;
42 import voxelman.world.storage.chunk;
43 import voxelman.world.storage.coordinates;
44 import voxelman.world.storage.utils;
45 import voxelman.world.storage.worldaccess;
46 import voxelman.utils.textformatter;
47 
48 import voxelman.client.console;
49 
50 //version = manualGC;
51 
52 
53 auto formatDuration(Duration dur)
54 {
55 	import std..string : format;
56 	auto splitted = dur.split();
57 	return format("%s.%03s,%03s secs",
58 		splitted.seconds, splitted.msecs, splitted.usecs);
59 }
60 
61 final class ClientPlugin : IPlugin
62 {
63 private:
64 	// Plugins
65 	EventDispatcherPlugin evDispatcher;
66 	GraphicsPlugin graphics;
67 	GuiPlugin guiPlugin;
68 	CommandPluginClient commandPlugin;
69 	ClientWorld clientWorld;
70 	NetClientPlugin connection;
71 	Debugger dbg;
72 
73 public:
74 	Console console;
75 	bool isConsoleShown = false;
76 
77 	// Client data
78 	bool isRunning = false;
79 
80 	double delta;
81 	Duration frameTime;
82 	ConfigOption maxFpsOpt;
83 	bool limitFps = true;
84 	FpsHelper fpsHelper;
85 
86 	// IPlugin stuff
87 	mixin IdAndSemverFrom!"voxelman.client.plugininfo";
88 
89 	override void registerResources(IResourceManagerRegistry resmanRegistry)
90 	{
91 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
92 		maxFpsOpt = config.registerOption!int("max_fps", true);
93 
94 		dbg = resmanRegistry.getResourceManager!Debugger;
95 
96 		KeyBindingManager keyBindingMan = resmanRegistry.getResourceManager!KeyBindingManager;
97 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_Q, "key.lockMouse", null, &onLockMouse));
98 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_GRAVE_ACCENT, "key.toggle_console", null, &onConsoleToggleKey));
99 	}
100 
101 	override void preInit()
102 	{
103 		fpsHelper.maxFps = maxFpsOpt.get!uint;
104 		if (fpsHelper.maxFps == 0) fpsHelper.limitFps = false;
105 		console.init();
106 	}
107 
108 	override void init(IPluginManager pluginman)
109 	{
110 		clientWorld = pluginman.getPlugin!ClientWorld;
111 		evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
112 
113 		graphics = pluginman.getPlugin!GraphicsPlugin;
114 		guiPlugin = pluginman.getPlugin!GuiPlugin;
115 		auto debugClient = pluginman.getPlugin!DebugClient;
116 		debugClient.registerDebugGuiHandler(&printFpsDebug, FPS_ORDER, "Fps");
117 
118 		evDispatcher.subscribeToEvent(&onPreUpdateEvent);
119 		evDispatcher.subscribeToEvent(&onPostUpdateEvent);
120 		evDispatcher.subscribeToEvent(&onClosePressedEvent);
121 
122 		commandPlugin = pluginman.getPlugin!CommandPluginClient;
123 		commandPlugin.registerCommand("cl_stop|stop", &onStopCommand);
124 
125 		console.messageWindow.messageHandler = &onConsoleCommand;
126 
127 		connection = pluginman.getPlugin!NetClientPlugin;
128 	}
129 
130 	override void postInit() {}
131 
132 	void onStopCommand(CommandParams) { isRunning = false; }
133 
134 	void printFpsDebug()
135 	{
136 		igTextf("FPS: %s", fpsHelper.fps); igSameLine();
137 		int fpsLimitVal = maxFpsOpt.get!int;
138 		igPushItemWidth(60);
139 		igSliderInt(limitFps ? "limited##limit" : "unlimited##limit",
140 			&fpsLimitVal, 0, 240, null);
141 		igPopItemWidth();
142 		maxFpsOpt.set!int(fpsLimitVal);
143 		updateFrameTime();
144 	}
145 
146 	void run(string[] args)
147 	{
148 		import std.datetime : MonoTime, Duration, usecs, dur;
149 		import core.thread : Thread;
150 
151 		version(manualGC) GC.disable;
152 
153 		evDispatcher.postEvent(GameStartEvent());
154 
155 		MonoTime prevTime = MonoTime.currTime;
156 		updateFrameTime();
157 
158 		isRunning = true;
159 		ulong frame;
160 		while(isRunning)
161 		{
162 			MonoTime newTime = MonoTime.currTime;
163 			delta = (newTime - prevTime).total!"usecs" / 1_000_000.0;
164 			prevTime = newTime;
165 
166 				evDispatcher.postEvent(PreUpdateEvent(delta, frame));
167 				evDispatcher.postEvent(UpdateEvent(delta, frame));
168 				evDispatcher.postEvent(PostUpdateEvent(delta, frame));
169 				evDispatcher.postEvent(DoGuiEvent(frame));
170 				evDispatcher.postEvent(RenderEvent());
171 
172 				version(manualGC) {
173 					auto collectStartTime = MonoTime.currTime;
174 					GC.collect();
175 					GC.minimize();
176 					auto collectDur = MonoTime.currTime - collectStartTime;
177 					double collectDurFloat = collectDur.total!"usecs" / 1_000.0;
178 					dbg.logVar("GC, ms", collectDurFloat, 512);
179 				}
180 
181 				if (limitFps) {
182 					Duration updateTime = MonoTime.currTime - newTime;
183 					Duration sleepTime = frameTime - updateTime;
184 					if (sleepTime > Duration.zero)
185 						Thread.sleep(sleepTime);
186 				}
187 
188 				++frame;
189 		}
190 
191 		infof("Stopping...");
192 		evDispatcher.postEvent(GameStopEvent());
193 	}
194 
195 	void updateFrameTime()
196 	{
197 		uint maxFps = maxFpsOpt.get!uint;
198 		if (maxFps == 0) {
199 			limitFps = false;
200 			frameTime = Duration.zero;
201 			return;
202 		}
203 
204 		limitFps = true;
205 		frameTime = (1_000_000 / maxFpsOpt.get!uint).usecs;
206 	}
207 
208 	void onPreUpdateEvent(ref PreUpdateEvent event)
209 	{
210 		fpsHelper.update(event.deltaTime);
211 	}
212 
213 	void onPostUpdateEvent(ref PostUpdateEvent event)
214 	{
215 		import std.compiler;
216 		static if (version_minor >= 72)
217 		{
218 			import core.memory;
219 			dbg.logVar("GC used", core.memory.GC.stats().usedSize, 128);
220 			dbg.logVar("GC free", core.memory.GC.stats().freeSize, 128);
221 		}
222 
223 		if (isConsoleShown)
224 			console.draw();
225 		dbg.logVar("delta, ms", delta*1000.0, 256);
226 
227 		if (guiPlugin.mouseLocked)
228 			drawOverlay();
229 	}
230 
231 	void onConsoleCommand(string command)
232 	{
233 		infof("Executing command '%s'", command);
234 		ExecResult res = commandPlugin.execute(command, SessionId(0));
235 
236 		if (res.status == ExecStatus.notRegistered)
237 		{
238 			if (connection.isConnected)
239 				connection.send(CommandPacket(command));
240 			else
241 				console.lineBuffer.putfln("Unknown client command '%s', not connected to server", command);
242 		}
243 		else if (res.status == ExecStatus.error)
244 			console.lineBuffer.putfln("Error executing command '%s': %s", command, res.error);
245 		else
246 			console.lineBuffer.putln(command);
247 	}
248 
249 	void onConsoleToggleKey(string)
250 	{
251 		isConsoleShown = !isConsoleShown;
252 	}
253 
254 	void onClosePressedEvent(ref ClosePressedEvent event)
255 	{
256 		isRunning = false;
257 	}
258 
259 	void onLockMouse(string)
260 	{
261 		guiPlugin.toggleMouseLock();
262 	}
263 
264 	void drawOverlay()
265 	{
266 		vec2 winSize = graphics.window.size;
267 		vec2 center = ivec2(winSize / 2);
268 
269 		//enum float thickness = 1;
270 		//enum float cross_size = 20;
271 		//vec2 hor_size = vec2(cross_size, thickness);
272 		//vec2 vert_size = vec2(thickness, cross_size);
273 
274 		vec2 box_size = vec2(6, 6);
275 
276 		graphics.overlayBatch.putRect(center - box_size/2, box_size, Colors.white, false);
277 	}
278 }