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 }