/// <summary> /// Renders the standard view's 3D data. /// </summary> /// <param name="view">The view object.</param> public void Render3D(View3D view) { try { StackNoteHelper.Push("GameEngine3D - Render All Entities", this); // TODO: Out View Rendering! GL.ActiveTexture(TextureUnit.Texture1); Textures.NormalDef.Bind(); GL.ActiveTexture(TextureUnit.Texture0); foreach (ClientEntity ce in EntityList) { // TODO: layering logic of some form instead of this overly basic stuff. if (ShouldRender(ce.Renderer, view.RenderingShadows)) { try { StackNoteHelper.Push("GameEngine3D - Render Specific Entity", ce); ce.Renderer?.RenderStandard(MainContext); } finally { StackNoteHelper.Pop(); } } } } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Starts the game engine, and begins the primary loop. /// </summary> /// <param name="initialFlags">The initial window flag.</param> public void Start(GameWindowFlags initialFlags = GameWindowFlags.FixedWindow) { try { StackNoteHelper.Push("GameClientWindow - Start, run", this); SysConsole.Output(OutputType.INIT, "GameEngine loading..."); Window = new GameWindow(WindWid, WindHei, new GraphicsMode(24, 24, 0, 0), StartingWindowTitle, initialFlags, DisplayDevice.Default, 4, 3, GraphicsContextFlags.ForwardCompatible); Window.Load += Window_Load; Window.RenderFrame += Window_RenderFrame; Window.Mouse.Move += Mouse_Move; Window.Closed += Window_Closed; Window.Resize += Window_Resize; Window.ReduceCPUWaste = CPUWastePatch; SysConsole.Output(OutputType.INIT, "GameEngine calling SetUp event..."); OnWindowSetUp?.Invoke(); SysConsole.Output(OutputType.INIT, "GameEngine running..."); if (CPUWastePatch) { double rate = DisplayDevice.Default.RefreshRate; Window.Run(rate, rate); } else { Window.Run(); } } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Removes an entity from the world. /// </summary> /// <param name="ent">The entity to remove.</param> public void DespawnEntity(T ent) { if (!ent.IsSpawned) { SysConsole.Output(OutputType.WARNING, "Despawing non-spawned entity."); return; } try { StackNoteHelper.Push("BasicEngine - Despawn Entity", ent); foreach (Property prop in ent.EnumerateAllProperties()) { if (prop is BasicEntityProperty <T, T2> bep) { bep.OnDespawn(); } } ent.OnDespawnEvent?.Fire(new EntityDespawnEventArgs()); RemoveEntity(ent); ent.IsSpawned = false; } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Renders all entities and render helpers. /// </summary> /// <param name="lights">Whether to include things that don't cast shadows.</param> /// <param name="shouldShadow">The method to determine if an object should cast a shadow.</param> private void RenderAll(bool lights, Func <ClientEntity, bool> shouldShadow) { try { StackNoteHelper.Push("GameEngine2D - RenderAll", this); Textures.White.Bind(); RenderHelper.SetColor(Vector4.One); RenderAllObjectsPre?.Invoke(lights); // This dups the list inherently, preventing glitches from removal while rendering, helpfully! foreach (ClientEntity ent in Entities.Values .Where((e) => ShouldRender(e.Renderer, lights) && (shouldShadow == null || shouldShadow(e))) .OrderBy((e) => e.Renderer.RenderingPriorityOrder)) { try { StackNoteHelper.Push("GameEngine2D - Render Specific Entity", ent); ent.Renderer.RenderStandard2D(MainRenderContext); } finally { StackNoteHelper.Pop(); } } RenderAllObjectsPost?.Invoke(lights); GraphicsUtil.CheckError("Render - all Entities rendered"); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Renders a single frame of the game, and also ticks. /// </summary> /// <param name="sender">Irrelevant.</param> /// <param name="e">Holds the frame time (delta).</param> private void Window_RenderFrame(object sender, FrameEventArgs e) { try { StackNoteHelper.Push("GameClientWindow - Render and tick frame", this); // First step: check delta if (e.Time <= 0.0) { return; } // Mouse handling PreviousMouse = CurrentMouse; CurrentMouse = Mouse.GetState(); // Standard pre-tick PreTick(e.Time); ErrorCode ec = GL.GetError(); while (ec != ErrorCode.NoError) { SysConsole.Output(OutputType.WARNING, "Uncaught GL Error: " + ec); ec = GL.GetError(); } // Second step: clear the screen GL.ClearBuffer(ClearBuffer.Color, 0, ScreenClearColor); GL.ClearBuffer(ClearBuffer.Depth, 0, DepthClear); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); GL.DrawBuffer(DrawBufferMode.Back); GraphicsUtil.CheckError("GameClient - Pre"); // Tick helpers Models.Update(GlobalTickTime); GraphicsUtil.CheckError("GameClient - PostModelUpdate"); // Third step: general game rendering CurrentEngine.RenderSingleFrame(); GraphicsUtil.CheckError("GameClient - PostMainEngine"); // Add the UI Layer too MainUI.Draw(); GraphicsUtil.CheckError("GameClient - PostUI"); // Fourth step: clean up! GL.BindTexture(TextureTarget.Texture2D, 0); GL.BindVertexArray(0); GL.UseProgram(0); // Semi-final step: Tick logic! GraphicsUtil.CheckError("GameClient - PreTick"); // Main instance tick. Tick(); // Primary UI tick MainUI.Tick(); Resized = false; GraphicsUtil.CheckError("GameClient - PostTick"); // Final step: Swap the render buffer onto the screen! Window.SwapBuffers(); GraphicsUtil.CheckError("GameClient - Post"); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Ticks the instance's scheduler. /// </summary> public void TickScheduler() { try { StackNoteHelper.Push("GameInstance - Tick Scheduler", Schedule); Schedule.RunAllSyncTasks(Delta); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Constructs the server game instance. /// </summary> public ServerGameInstance() { try { StackNoteHelper.Push("ServerGameInstance construction, preparating of default engine", this); Engines.Add(new ServerEngine()); DefaultEngine.LoadBasic(); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Ticks the instance and all engines. /// Called automatically by the standard run thread. /// Call <see cref="PreTick(double)"/> first. /// </summary> public void Tick() { try { StackNoteHelper.Push("GameInstance tick sequence - Tick", this); foreach (T2 engine in Engines) { engine.Delta = Delta; engine.Tick(); } } finally { StackNoteHelper.Pop(); } }
/// <summary> /// The internal engine tick sequence. /// </summary> public void Tick() { try { StackNoteHelper.Push("BasicEngine - Tick Scheduler", Schedule); Schedule.RunAllSyncTasks(Delta); } finally { StackNoteHelper.Pop(); } try { StackNoteHelper.Push("BasicEngine - Update Physics", PhysicsWorld); PhysicsWorld.Internal.Update(Delta); } finally { StackNoteHelper.Pop(); } try { StackNoteHelper.Push("BasicEngine - Tick all entities", this); // Dup list, to ensure ents can despawn themselves in the tick method! IReadOnlyList <T> ents = EntityListDuplicate(); for (int i = 0; i < ents.Count; i++) { if (ents[i].Ticks) { try { StackNoteHelper.Push("BasicEngine - Tick specific entity", ents[i]); ents[i].TickThis(); } finally { StackNoteHelper.Pop(); } } } } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Ticks the server and all engines. /// Called automatically by the standard run thread. /// </summary> /// <param name="delta">How much time has passed since the last tick.</param> public void Tick(double delta) { try { StackNoteHelper.Push("ServerGameInstance tick sequence - Tick", this); tpsc++; Delta = delta; GlobalTickTime += delta; Schedule.RunAllSyncTasks(delta); foreach (ServerEngine engine in Engines) { engine.Delta = delta; engine.Tick(); } } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Loads the engine. /// </summary> public void Load() { try { StackNoteHelper.Push("GameEngineBase - Loading", this); SysConsole.Output(OutputType.INIT, "GameEngine starting load sequence, start with basic..."); LoadBasic(); SysConsole.Output(OutputType.INIT, "GameEngine loading shaders..."); GetShaders(); SysConsole.Output(OutputType.INIT, "GameEngine core load complete, calling additional load..."); PostLoad(); SysConsole.Output(OutputType.INIT, "GameEngine prepping audio systems..."); Sounds = new SoundEngine(); Sounds.Init(this); SysConsole.Output(OutputType.INIT, "GameEngine load sequence complete."); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Spawns an entity into the world. /// </summary> /// <param name="ticks">Whether it should tick.</param> /// <param name="configure">A method to configure the entity prior to spawn or property add, if one applies.</param> /// <param name="props">Any properties to apply.</param> /// <returns>The spawned entity.</returns> public T SpawnEntity(bool ticks, Action <T> configure, params Property[] props) { try { StackNoteHelper.Push("BasicEngine - Spawn Entity", this); T ce = CreateEntity(ticks); ce.EID = CurrentEID++; try { StackNoteHelper.Push("BasicEngine - Configure Entity", ce); configure?.Invoke(ce); for (int i = 0; i < props.Length; i++) { ce.AddProperty(props[i]); } AddEntity(ce); ce.IsSpawned = true; foreach (Property prop in ce.GetAllProperties()) { if (prop is BasicEntityProperty <T, T2> bep) { bep.OnSpawn(); } } ce.OnSpawnEvent?.Fire(new EntitySpawnEventArgs()); } finally { StackNoteHelper.Pop(); } return(ce); } finally { StackNoteHelper.Pop(); } }
/// <summary> /// Starts and runs the entire server game instance. /// Will take over present thread until completion. /// </summary> public void StartAndRun() { // Tick double TARGETFPS = 30.0; Stopwatch Counter = new Stopwatch(); DeltaCounter = new Stopwatch(); DeltaCounter.Start(); TotalDelta = 0; double CurrentDelta = 0.0; TargetDelta = 0.0; int targettime = 0; try { StackNoteHelper.Push("ServerGameInstance main loop - StartAndRun", this); while (true) { // Update the tick time usage counter Counter.Reset(); Counter.Start(); // Update the tick delta counter DeltaCounter.Stop(); // Delta time = Elapsed ticks * (ticks/second) CurrentDelta = ((double)DeltaCounter.ElapsedTicks) / ((double)Stopwatch.Frequency); // Begin the delta counter to find out how much time is /really/ slept+ticked for DeltaCounter.Reset(); DeltaCounter.Start(); // How much time should pass between each tick ideally TARGETFPS = Target_FPS; if (TARGETFPS < 1 || TARGETFPS > 600) { TARGETFPS = 30; } TargetDelta = (1.0d / TARGETFPS); // How much delta has been built up TotalDelta += CurrentDelta; double tdelt = TargetDelta; while (TotalDelta > tdelt * 3) { // Lagging - cheat to catch up! tdelt *= 2; // TODO: Handle even harder tick loss } // As long as there's more delta built up than delta wanted, tick while (TotalDelta > tdelt) { if (NeedShutdown) { return; } lock (TickLock) { Tick(tdelt); } TotalDelta -= tdelt; } // The tick is done, stop measuring it Counter.Stop(); // Only sleep for target milliseconds/tick minus how long the tick took... this is imprecise but that's okay targettime = (int)((1000d / TARGETFPS) - Counter.ElapsedMilliseconds); // Only sleep at all if we're not lagging if (targettime > 0) { // Try to sleep for the target time - very imprecise, thus we deal with precision inside the tick code Thread.Sleep(targettime); } } } catch (ThreadAbortException) { return; } catch (Exception ex) { SysConsole.Output("Server crash", ex); } finally { StackNoteHelper.Pop(); } }