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 }