/
EugenyCh7
/
Puppet2D.Net
Обзор
Документация
Войти
/
EugenyCh7
/
Puppet2D.Net
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Puppet2D.Engine/GameWindows/GameWindow.cs
695 строк
22 KB
EugenyCh7
UpdateComponent(float milliseconds)
25 авг 2025, 21:13
25 авг 2025, 21:13
3101026
Код
Авторство
О чём код?
using Puppet2D.Engine.Common; using Puppet2D.Engine.Coroutines; using Puppet2D.Engine.Worlds; using Puppet2D.Graphics; using Puppet2D.System; using Puppet2D.Window; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace Puppet2D.Engine.GameWindows { public class GameWindow { private readonly SortedDictionary<int, Camera> cameras = new SortedDictionary<int, Camera>(); private readonly SortedDictionary<int, Canvas> canvases = new SortedDictionary<int, Canvas>(); private readonly ConcurrentDictionary<CoroutineKey, CancellationTokenSource> coroutines = new ConcurrentDictionary<CoroutineKey, CancellationTokenSource>(); private static readonly Font systemFont = new Font("Resources/Fonts/system.otf"); private readonly RenderWindow window; private View windowView; private bool isFirstStep; private long counterStepInMilliseconds = 250; private readonly ThresholdWait thresholdWait = new ThresholdWait(10); private Stopwatch physicsStopwatch; private int physicsFrames; private long physicsElapsedTicks; private Stopwatch renderStopwatch; private int renderFrames; private long renderElapsedTicks; private readonly Text hertzText = new Text() { Font = systemFont, Position = new Vector2(8, 8), Style = Text.Styles.Bold, CharacterSize = 18, FillColor = Color.Cyan, OutlineColor = Color.Black, OutlineThickness = 2 }; #region Properties /// <summary> /// An active world. /// </summary> public World World { get; set; } /// <summary> /// The number of sub-steps, increasing the sub-step count can increase accuracy. Usually 4. /// </summary> public int SubStepCount { get; set { if (value <= 0) throw new ArgumentException($"The value of {nameof(SubStepCount)} must be greater than 0."); field = value; } } /// <summary> /// Reflection of the horizontal coordinate axis. /// </summary> public bool FlipX { get; } /// <summary> /// Reflection of the vertical coordinate axis. /// </summary> public bool FlipY { get; } /// <summary> /// A vector representing the orientation of the coordinate axes. /// </summary> public Vector2 Flip { get; } /// <summary> /// The pause of physical processes in the world. /// </summary> public bool Pause { get; set; } /// <summary> /// Time scaling for physical processes. /// </summary> public float PhysicsTimeScaling { get; set { if (value <= 0) throw new ArgumentException($"The value of {nameof(PhysicsTimeScaling)} must be greater than 0."); field = value; } } = 1.0f; /// <summary> /// Draw only canvases. /// </summary> public bool OnlyCanvas { get; set; } /// <summary> /// Screen zoom level (1.0 by default). /// </summary> public float Zoom { get; set { if (value <= 0) throw new ArgumentException($"The value of {nameof(Zoom)} must be greater than 0."); field = value; Resize(WindowSize); } } = 1.0f; /// <summary> /// The size of the window in pixels. /// </summary> public Vector2u WindowSize { get; internal set; } /// <summary> /// The initial size of the window in pixels. /// </summary> public Vector2u InitialWindowSize { get; internal set; } /// <summary> /// The frequency of one physical step (steps per second). /// </summary> public float PhysicsFrequency { get; set { if (value < 1) throw new ArgumentException($"The value of {nameof(PhysicsFrequency)} must be greater than or equal to 1."); field = value; PhysicsTime = 1000.0f / field; } } /// <summary> /// The time of one physical step in milliseconds. /// </summary> public float PhysicsTime { get; private set; } /// <summary> /// The target frequency of rendering on the screen (frames per second). /// </summary> public uint TargetRenderFrequency { get; set { if (value < 1) throw new ArgumentException($"The value of {nameof(TargetRenderFrequency)} must be greater than or equal to 1."); field = value; TargetRenderTime = 1000.0f / field; window.SetFramerateLimit(field); } } /// <summary> /// The target time of one render step in milliseconds. /// </summary> public float TargetRenderTime { get; private set; } /// <summary> /// The frequency of last rendering on the screen (frames per second). /// </summary> public float RenderFrequency { get; private set; } /// <summary> /// The time of the last one render step in milliseconds. /// </summary> public float RenderTime { get; private set { field = value; RenderFrequency = 1000.0f / value; } } /// <summary> /// Whether to show the frame rate per second on the screen. /// </summary> public bool ShowFrequency { get; set; } /// <summary> /// The background color of the screen. /// </summary> public Color Background { get; set; } = Color.Black; /// <summary> /// Video mode. /// </summary> public VideoMode VideoMode { get; } /// <summary> /// The update step of the rendering and physics refresh rate counter. /// </summary> public TimeSpan CounterStep { get => TimeSpan.FromMilliseconds(counterStepInMilliseconds); set => counterStepInMilliseconds = (long)value.TotalMilliseconds; } #endregion Properties public GameWindow(GameWindowDefinition definition) { InitialWindowSize = definition.WindowSize; VideoMode = new VideoMode(InitialWindowSize.X, InitialWindowSize.Y); window = new RenderWindow(VideoMode, definition.Title, definition.Styles, definition.ContextSettings); TargetRenderFrequency = definition.RenderFrequency; PhysicsFrequency = definition.PhysicsFrequency; SubStepCount = definition.SubStepCount; FlipX = definition.FlipX; FlipY = definition.FlipY; Flip = new Vector2(FlipX ? -1 : 1, FlipY ? -1 : 1); window.Resized += Resized; window.Closed += Closed; window.KeyPressed += KeyPressed; window.KeyReleased += KeyReleased; window.MouseButtonPressed += MouseButtonPressed; window.MouseButtonReleased += MouseButtonReleased; window.MouseMoved += MouseMoved; Resize(definition.WindowSize); } private void Resize(Vector2u newSize) { WindowSize = newSize; windowView = new View() { Center = new Vector2(0.5f * newSize.X / Zoom, 0.5f * newSize.Y / Zoom), Size = new Vector2(newSize.X / Zoom, newSize.Y / Zoom) }; foreach (Camera camera in cameras.Values) { camera.UpdateSize(this); } foreach (Canvas canvas in canvases.Values) { canvas.UpdateSize(this); } } #region Coroutines private async Task WaitCoroutine(Coroutine coroutine, CancellationToken cancellationToken = default) { IEnumerator<CoroutineStep> enumerator = coroutine.Invoke().GetEnumerator(); while (true) { cancellationToken.ThrowIfCancellationRequested(); if (World != null) { lock (World) { if (!enumerator.MoveNext()) return; } } switch (enumerator.Current) { case WaitForTime c: thresholdWait.Wait(c.Duration, cancellationToken); break; case WaitForPhysicsTime: thresholdWait.Wait(PhysicsTime, cancellationToken); break; case WaitForRenderTime: thresholdWait.Wait(RenderTime, cancellationToken); break; case WaitForTargetRenderTime: thresholdWait.Wait(TargetRenderTime, cancellationToken); break; case WaitForCoroutine c: await WaitCoroutine(c.Coroutine, cancellationToken).ContinueWith(t => { if (t.IsFaulted) throw t.Exception; }); break; default: throw new NotImplementedException($"The {enumerator.Current?.GetType()?.ToString() ?? "null"} coroutine step is not implemented!"); } } } public CoroutineKey StartCoroutine(Coroutine coroutine) { return StartCoroutine(new CoroutineKey(null, null), coroutine); } public CoroutineKey StartCoroutine(GameObject gameObject, Coroutine coroutine) { return StartCoroutine(new CoroutineKey(null, gameObject), coroutine); } public CoroutineKey StartCoroutine(string tag, Coroutine coroutine) { return StartCoroutine(new CoroutineKey(tag, null), coroutine); } public CoroutineKey StartCoroutine(string tag, GameObject gameObject, Coroutine coroutine) { return StartCoroutine(new CoroutineKey(tag, gameObject), coroutine); } private CoroutineKey StartCoroutine(CoroutineKey key, Coroutine coroutine) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); Task.Run(async delegate { await WaitCoroutine(coroutine, cancellationTokenSource.Token); }).ContinueWith(t => { coroutines.TryRemove(key, out _); if (t.IsFaulted) throw t.Exception; }); coroutines.TryAdd(key, cancellationTokenSource); return key; } public bool StopCoroutine(CoroutineKey key) { if (coroutines.TryRemove(key, out CancellationTokenSource cts)) { cts.Cancel(); return true; } return false; } public bool StopCoroutine(Predicate<CoroutineKey> condition) { foreach (CoroutineKey key in coroutines.Keys) { if (condition.Invoke(key) && coroutines.TryRemove(key, out CancellationTokenSource cts)) { cts.Cancel(); return true; } } return false; } public void StopAllCoroutines() { foreach (CancellationTokenSource cts in coroutines.Values) cts.Cancel(); coroutines.Clear(); } public void StopAllCoroutines(Predicate<CoroutineKey> condition) { foreach (CoroutineKey key in coroutines.Keys) { if (condition.Invoke(key) && coroutines.TryRemove(key, out CancellationTokenSource cts)) cts.Cancel(); } } #endregion Coroutines #region Game Process /// <summary> /// Locked action to be invoked. /// </summary> public void Invoke(Action action) { if (World != null) { lock (World) { action.Invoke(); } } } private void UpdateCounter() { if (renderStopwatch == null || physicsStopwatch == null) return; if (renderStopwatch.ElapsedMilliseconds >= counterStepInMilliseconds) { double renderAverageTime = TimeSpan.FromTicks(renderElapsedTicks).TotalMicroseconds / renderFrames; renderElapsedTicks = 0; double physicsAverageTime = TimeSpan.FromTicks(physicsElapsedTicks).TotalMicroseconds / physicsFrames; physicsElapsedTicks = 0; double renderHertz = renderFrames * 1000.0 / renderStopwatch.ElapsedMilliseconds; renderFrames = 0; renderStopwatch.Restart(); double physicsHertz = physicsFrames * 1000.0 / physicsStopwatch.ElapsedMilliseconds; physicsFrames = 0; physicsStopwatch.Restart(); hertzText.DisplayedString = $"Render: {renderHertz,6:F1} Hz {renderAverageTime,7:F0} μs\n" + $"Physics: {physicsHertz,6:F1} Hz {physicsAverageTime,7:F0} μs"; } } private void UpdatePhysics() { if (ShowFrequency) { physicsFrames++; if (physicsStopwatch == null) physicsStopwatch = Stopwatch.StartNew(); } else { physicsFrames = 0; physicsStopwatch = null; } if (World != null && !Pause && !OnlyCanvas) { Stopwatch sw = Stopwatch.StartNew(); lock (World) { if (isFirstStep) { isFirstStep = false; World.Initialize(); } World.StepStart(); World.UpdateGameObjects(PhysicsTime * PhysicsTimeScaling); World.Step(); World.PhysicsStep(PhysicsTime * PhysicsTimeScaling * 0.001f, SubStepCount); World.StepEnd(); foreach (Camera camera in cameras.Values) { if (camera.IsActive) { camera.UpdateCenter(); } } } sw.Stop(); physicsElapsedTicks += sw.ElapsedTicks; } } private void Render() { if (ShowFrequency) { renderFrames++; if (renderStopwatch == null) renderStopwatch = Stopwatch.StartNew(); } else { renderFrames = 0; renderStopwatch = null; } Stopwatch sw = Stopwatch.StartNew(); window.Clear(Background); if (!OnlyCanvas && World != null) { foreach (Camera camera in cameras.Values) { if (camera.IsActive) { window.Draw(camera); View view = new View(camera.view); view.Size *= Flip; lock (World) { window.SetView(view); World.BeforeRender(window); window.SetView(view); World.Render(window); window.SetView(view); World.AfterRender(window); } } } } foreach (Canvas canvas in canvases.Values) { window.Draw(canvas); } if (ShowFrequency) { window.SetView(windowView); window.Draw(hertzText); } window.Display(); sw.Stop(); renderElapsedTicks += sw.ElapsedTicks; RenderTime = (float)sw.Elapsed.TotalMilliseconds; } /// <summary> /// Start the gameplay with this world. /// </summary> public void Run() { renderElapsedTicks = 0; renderFrames = 0; renderStopwatch = null; physicsElapsedTicks = 0; physicsFrames = 0; physicsStopwatch = null; Stopwatch stopwatch = Stopwatch.StartNew(); double accumulator = 0.0; thresholdWait.Reset(); isFirstStep = true; while (window.IsOpen) { window.DispatchEvents(); double deltaTime = stopwatch.Elapsed.TotalMilliseconds; stopwatch.Restart(); accumulator += deltaTime; while (accumulator >= PhysicsTime) { UpdatePhysics(); accumulator -= PhysicsTime; } UpdateCounter(); Render(); } } #endregion Game Process #region GetWorldPoint public Vector2 GetWorldPoint(int windowPointX, int windowPointY, Camera camera) { return GetWorldPoint(new Vector2i(windowPointX, windowPointY), camera.view); } public Vector2 GetWorldPoint(int windowPointX, int windowPointY, View view) { return GetWorldPoint(new Vector2i(windowPointX, windowPointY), view); } public Vector2 GetWorldPoint(Vector2i windowPoint, Camera camera) { return GetWorldPoint(windowPoint, camera.view); } public Vector2 GetWorldPoint(Vector2i windowPoint, View view) { FloatRect rect = view.Viewport; Vector2u windowSize = window.Size; Vector2 point = new Vector2(windowPoint.X, windowPoint.Y); if (FlipX) point.X = windowSize.X - point.X; if (FlipY) point.Y = windowSize.Y - point.Y; point.X -= windowSize.X * rect.Left; point.Y -= windowSize.Y * rect.Top; point.X /= rect.Width; point.Y /= rect.Height; return new Vector2() { X = view.Center.X + view.Size.X * (point.X / windowSize.X - 0.5f), Y = view.Center.Y + view.Size.Y * (point.Y / windowSize.Y - 0.5f) }; } #endregion GetWorldPoint #region Cameras /// <summary> /// Assign a camera by a specific index in the sorted dictionary. /// </summary> public void SetCamera(int index, Camera camera) { cameras[index] = camera; camera.UpdateSize(this); } /// <summary> /// Exclude the camera from the dictionary. /// </summary> /// <param name="index"></param> public void RemoveCamera(int index) { cameras.Remove(index); } #endregion Cameras #region Canvases public void SetCanvas(int index, Canvas canvas) { canvases[index] = canvas; canvas.UpdateSize(this); } public void RemoveCanvas(int index) { canvases.Remove(index); } #endregion Canvases #region Events public virtual void Resized(object sender, SizeEventArgs e) { Resize(new Vector2u(e.Width, e.Height)); World?.WindowResized(sender, e); } public virtual void Closed(object sender, EventArgs e) { World?.WindowClosed(sender, e); window.Close(); } public virtual void KeyPressed(object sender, KeyEventArgs e) { World?.KeyPressed(sender, e); } public virtual void KeyReleased(object sender, KeyEventArgs e) { World?.KeyReleased(sender, e); } public virtual void MouseButtonPressed(object sender, MouseButtonEventArgs e) { foreach (Canvas canvas in canvases.Values) { canvas.MousePressed(this, e); } World?.MouseButtonPressed(this, e); } public virtual void MouseButtonReleased(object sender, MouseButtonEventArgs e) { foreach (Canvas canvas in canvases.Values) { canvas.MouseReleased(this, e); } World?.MouseButtonReleased(this, e); } public virtual void MouseMoved(object sender, MouseMoveEventArgs e) { foreach (Canvas canvas in canvases.Values) { canvas.MouseMoved(this, e); } World?.MouseMoved(this, e); } #endregion Events } }