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 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 
15 import voxelman.utils.fpshelper;
16 import voxelman.graphics.gl;
17 import voxelman.gui;
18 import voxelman.gui.textedit.lineedit;
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.text.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 	GuiContext guictx;
73 
74 public:
75 	Console console;
76 
77 	// Client data
78 	bool isRunning = false;
79 
80 	double delta = 0;
81 	double updateTime = 0;
82 	//Duration targetFrameTime;
83 	ConfigOption maxFpsOpt;
84 	ConfigOption limitFpsOpt;
85 	bool limitFps = true;
86 	FpsHelper fpsHelper;
87 
88 	// IPlugin stuff
89 	mixin IdAndSemverFrom!"voxelman.client.plugininfo";
90 
91 	override void registerResources(IResourceManagerRegistry resmanRegistry)
92 	{
93 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
94 		maxFpsOpt = config.registerOption!int("max_fps", 120);
95 		limitFpsOpt = config.registerOption!bool("limit_fps", true);
96 
97 		dbg = resmanRegistry.getResourceManager!Debugger;
98 
99 		KeyBindingManager keyBindingMan = resmanRegistry.getResourceManager!KeyBindingManager;
100 		keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_GRAVE_ACCENT, "key.toggle_console", &console.onConsolePressKey, &console.onConsoleReleaseKey, FireBeforeInput.yes));
101 	}
102 
103 	override void preInit()
104 	{
105 		fpsHelper.maxFps = maxFpsOpt.get!uint;
106 		limitFps = limitFpsOpt.get!bool;
107 	}
108 
109 	override void init(IPluginManager pluginman)
110 	{
111 		clientWorld = pluginman.getPlugin!ClientWorld;
112 		evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
113 
114 		graphics = pluginman.getPlugin!GraphicsPlugin;
115 		guiPlugin = pluginman.getPlugin!GuiPlugin;
116 		guictx = guiPlugin.guictx;
117 		auto debugClient = pluginman.getPlugin!DebugClient;
118 		debugClient.registerDebugGuiHandler(&printFpsDebug, FPS_ORDER, "Fps");
119 
120 		evDispatcher.subscribeToEvent(&onPreUpdateEvent);
121 		evDispatcher.subscribeToEvent(&onPostUpdateEvent);
122 		evDispatcher.subscribeToEvent(&onClosePressedEvent);
123 		evDispatcher.subscribeToEvent(&onMessageEvent);
124 
125 		commandPlugin = pluginman.getPlugin!CommandPluginClient;
126 		commandPlugin.registerCommand(CommandInfo("stop|cl_stop", &onStopCommand, null, "Stops the client"));
127 
128 		connection = pluginman.getPlugin!NetClientPlugin;
129 
130 		console.create(guictx, &onConsoleEnter);
131 
132 		if (graphics.vsync.get!bool)
133 			limitFps = false;
134 	}
135 
136 	void onMessageEvent(ref MessageEvent event)
137 	{
138 		if (event.endpoint == MessageEndpoint.integratedConsole)
139 		{
140 			console.messages.putln(event.msg);
141 		}
142 	}
143 
144 	void onConsoleEnter(string command)
145 	{
146 		commandPlugin.execute(command, CommandSourceType.clientConsole, SessionId(0));
147 		console.messages.putln(commandPlugin.commandTextOutput.text);
148 	}
149 
150 	override void postInit() {}
151 
152 	void onStopCommand(CommandParams) { isRunning = false; }
153 
154 	void printFpsDebug()
155 	{
156 		//igTextf("FPS: %s", fpsHelper.fps); igSameLine();
157 		//int fpsLimitVal = maxFpsOpt.get!int;
158 		//igPushItemWidth(60);
159 		//igSliderInt("##limit_val", &fpsLimitVal, 30, 240, null);
160 		//igPopItemWidth();
161 		//igSameLine();
162 		//igCheckbox("limit##limit_fps_toggle", &limitFps);
163 		//maxFpsOpt.set!int(fpsLimitVal);
164 		dbg.setVar("show console", console.isConsoleShown);
165 	}
166 
167 	void run(string[] args)
168 	{
169 		import core.time : MonoTime, Duration, usecs, dur;
170 		import core.thread : Thread;
171 
172 		version(manualGC) GC.disable;
173 
174 		evDispatcher.postEvent(GameStartEvent());
175 
176 		MonoTime prevTime = MonoTime.currTime;
177 
178 		isRunning = true;
179 		ulong frame;
180 		while(isRunning)
181 		{
182 			MonoTime newTime = MonoTime.currTime;
183 			delta = (newTime - prevTime).total!"usecs" / 1_000_000.0;
184 			prevTime = newTime;
185 
186 			fpsHelper.update(delta, updateTime);
187 
188 			graphics.debugText.putfln("FPS: %.1f", fpsHelper.fps);
189 			evDispatcher.postEvent(PreUpdateEvent(delta, frame));
190 			evDispatcher.postEvent(UpdateEvent(delta, frame));
191 			evDispatcher.postEvent(PostUpdateEvent(delta, frame));
192 			evDispatcher.postEvent(DoGuiEvent(frame));
193 			evDispatcher.postEvent(RenderEvent());
194 
195 			version(manualGC) {
196 				auto collectStartTime = MonoTime.currTime;
197 				GC.collect();
198 				GC.minimize();
199 				auto collectDur = MonoTime.currTime - collectStartTime;
200 				double collectDurFloat = collectDur.total!"usecs" / 1_000.0;
201 				dbg.logVar("GC, ms", collectDurFloat, 512);
202 			}
203 
204 			Duration updateTimeDur = MonoTime.currTime - newTime;
205 			updateTime = updateTimeDur.total!"usecs" / 1_000_000.0;
206 
207 			if (limitFps) {
208 				Duration sleepTime = targetFrameTime - updateTimeDur;
209 				if (sleepTime > Duration.zero)
210 					Thread.sleep(sleepTime);
211 			}
212 
213 			++frame;
214 		}
215 
216 		infof("Stopping...");
217 		evDispatcher.postEvent(GameStopEvent());
218 	}
219 
220 	Duration targetFrameTime()
221 	{
222 		return (1_000_000 / maxFpsOpt.get!uint).usecs;
223 	}
224 
225 	void onPreUpdateEvent(ref PreUpdateEvent event)
226 	{
227 	}
228 
229 	void onPostUpdateEvent(ref PostUpdateEvent event)
230 	{
231 		import std.compiler;
232 		//static if (version_minor >= 72)
233 		//{
234 		//	import core.memory;
235 		//	dbg.logVar("GC used", core.memory.GC.stats().usedSize, 128);
236 		//	dbg.logVar("GC free", core.memory.GC.stats().freeSize, 128);
237 		//}
238 
239 		dbg.logVar("delta, ms", delta*1000.0, 256);
240 
241 		if (guiPlugin.mouseLocked)
242 			drawOverlay();
243 	}
244 
245 	void onClosePressedEvent(ref ClosePressedEvent event)
246 	{
247 		isRunning = false;
248 	}
249 
250 	void drawOverlay()
251 	{
252 		vec2 winSize = graphics.window.size;
253 		vec2 center = ivec2(winSize / 2);
254 
255 		//enum float thickness = 1;
256 		//enum float cross_size = 20;
257 		//vec2 hor_size = vec2(cross_size, thickness);
258 		//vec2 vert_size = vec2(thickness, cross_size);
259 
260 		vec2 box_size = vec2(6, 6);
261 
262 		graphics.overlayBatch.putRect(center - box_size/2, box_size, Colors.white, false);
263 	}
264 }