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 }