UNPKG

jsge

Version:

Javascript Game Engine

444 lines (384 loc) 17.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="expires" content="Wen, 05 Jun 2024 07:01:00 GMT"> <title>JSDoc: Source: base/RenderLoop.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="stage-title">Source: base/RenderLoop.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import { SystemSettings } from "../configs.js"; import { GameStageData } from "./GameStageData.js"; import { CONST } from "../constants.js"; import { Warning } from "./Exception.js"; import { WARNING_CODES } from "../constants.js"; import { DrawTiledLayer } from "./2d/DrawTiledLayer.js"; import { DrawImageObject } from "./2d/DrawImageObject.js"; import { DrawCircleObject } from "./2d/DrawCircleObject.js"; import { DrawConusObject } from "./2d/DrawConusObject.js"; import { DrawLineObject } from "./2d/DrawLineObject.js"; import { DrawPolygonObject } from "./2d/DrawPolygonObject.js"; import { DrawRectObject } from "./2d/DrawRectObject.js"; import { DrawTextObject } from "./2d/DrawTextObject.js"; import { WebGlEngine } from "./WebGl/WebGlEngine.js"; import { RenderLoopDebug } from "./RenderLoopDebug.js"; import { utils } from "../index.js"; /** * Class represents the render loop, * on each time stage start, a new RenderLoop class instance created, * after stage stop, RenderLoop stops and its instance removed * @see {@link IRender} a part of iRender * @hideconstructor */ export class RenderLoop { /** * @type {boolean} */ #isActive; /** * @type {boolean} */ #isCleared; /** * @type {RenderLoopDebug} */ #renderLoopDebug; #fpsAverageCountTimer; /** * * @type {GameStageData} */ #stageData; /** * @type { WebGlEngine } */ #webGlEngine; /** * * @type {SystemSettings} */ #systemSettings; /** * @type {EventTarget} */ #emitter = new EventTarget(); constructor(systemSettings, stageData, WebGlEngine) { this.#systemSettings = systemSettings; this.#stageData = stageData; this.#renderLoopDebug = new RenderLoopDebug(this.#systemSettings.gameOptions.render.cyclesTimeCalc.averageFPStime); this.#webGlEngine = WebGlEngine; this.#webGlEngine._initDrawCallsDebug(this.renderLoopDebug); if (this.#systemSettings.gameOptions.render.cyclesTimeCalc.check === CONST.OPTIMIZATION.CYCLE_TIME_CALC.AVERAGES) { this.#fpsAverageCountTimer = setInterval(() => this.#countFPSaverage(), this.#systemSettings.gameOptions.render.cyclesTimeCalc.averageFPStime); } } /** * @returns { GameStageData } */ get stageData() { return this.#stageData; } /** * @returns { RenderLoopDebug } */ get renderLoopDebug() { return this.#renderLoopDebug; } /** * @ignore */ set _isCleared(value) { this.#isCleared = value; } /** * @ignore */ get _isCleared() { return this.#isCleared; } _start() { this.#isActive = true; requestAnimationFrame(this.#runRenderLoop); } _stop() { this.#isActive = false; this.#stageData = null; this.renderLoopDebug.cleanupTempVars(); clearInterval(this.#fpsAverageCountTimer); //this.#fpsAverageCountTimer = null; } /** * * @param {Number} drawTimestamp - end time of previous frame's rendering */ #runRenderLoop = (drawTimestamp) => { if (!this.#isActive) { return; } const currentDrawTime = this.renderLoopDebug.currentDrawTime(drawTimestamp); this.renderLoopDebug.prevDrawTime = drawTimestamp; const timeStart = performance.now(), isCyclesTimeCalcCheckCurrent = this.#systemSettings.gameOptions.render.cyclesTimeCalc.check === CONST.OPTIMIZATION.CYCLE_TIME_CALC.CURRENT; this.emit(CONST.EVENTS.SYSTEM.RENDER.START); this.#stageData._clearBoundaries(); this.#clearContext(); this.render().then(() => { const currentRenderTime = performance.now() - timeStart; if (isCyclesTimeCalcCheckCurrent) { console.log("current draw take: ", (currentDrawTime), " ms"); console.log("current render() time: ", currentRenderTime); console.log("draw calls: ", this.renderLoopDebug.drawCalls); console.log("vertices draw: ", this.renderLoopDebug.verticesDraw); } else { this.renderLoopDebug.tempRCircleT = currentDrawTime; this.renderLoopDebug.incrementTempRCircleTPointer(); } this.emit(CONST.EVENTS.SYSTEM.RENDER.END); if (this.#isActive) { setTimeout(() => requestAnimationFrame(this.#runRenderLoop)); } }).catch((errors) => { if (errors.forEach) { errors.forEach((err) => { Warning(WARNING_CODES.UNHANDLED_DRAW_ISSUE, err); }); } else { Warning(WARNING_CODES.UNHANDLED_DRAW_ISSUE, errors.message); } this._stop(); }); }; /** * @returns {Promise&lt;void>} */ async render() { const renderObjects = this.#stageData.renderObjects; let errors = [], isErrors = false, len = renderObjects.length, renderObjectsPromises = new Array(len); if (len !== 0) { //this.#checkCollisions(view.renderObjects); for (let i = 0; i &lt; len; i++) { const object = renderObjects[i]; if (object.isRemoved) { renderObjects.splice(i, 1); i--; len--; continue; } if ("hasAnimations" in object &amp;&amp; object.hasAnimations) { object._processActiveAnimations(); } const promise = await this.#drawRenderObject(object) .catch((err) => Promise.reject(err)); renderObjectsPromises[i] = promise; } if (this.#systemSettings.gameOptions.debug.boundaries.drawLayerBoundaries) { renderObjectsPromises.push(this.#drawBoundariesWebGl() .catch((err) => Promise.reject(err))); } } const bindResults = await Promise.allSettled(renderObjectsPromises); bindResults.forEach((result) => { if (result.status === "rejected") { Promise.reject(result.reason); isErrors = true; errors.push(result.reason); } }); this._isCleared = false; if (isErrors === false) { this.#stageData._processPendingRenderObjects(); return Promise.resolve(); } else { return Promise.reject(errors); } } /** * * @param {string} eventName * @param {*} listener * @param {*=} options */ addEventListener = (eventName, listener, options) => { this.#emitter.addEventListener(eventName, listener, options); }; /** * * @param {string} eventName * @param {*} listener * @param {*=} options */ removeEventListener = (eventName, listener, options) => { this.#emitter.removeEventListener(eventName, listener, options); }; /** * * @param {string} eventName * @param {...any} eventParams */ emit = (eventName, ...eventParams) => { const event = new Event(eventName); event.data = [...eventParams]; this.#emitter.dispatchEvent(event); }; /** * @ignore * @param {DrawImageObject | DrawCircleObject | DrawConusObject | DrawLineObject | DrawPolygonObject | DrawRectObject | DrawTextObject | DrawTiledLayer} renderObject * @returns {Promise&lt;void>} */ #drawRenderObject(renderObject) { return this.#webGlEngine._preRender() .then(() => this.#isActive ? this.#webGlEngine._drawRenderObject(renderObject, this.stageData) : Promise.resolve()) .then((args) => this.#webGlEngine._postRender(args)); } #clearContext() { this.#webGlEngine._clearView(); } /** * * @returns {Promise&lt;void>} */ #drawBoundariesWebGl() { return new Promise((resolve) => { const b = this.stageData.getRawBoundaries(), eB = this.stageData.getEllipseBoundaries(), pB = this.stageData.getPointBoundaries(), bDebug = this.stageData.getDebugObjectBoundaries(), len = this.stageData.boundariesLen, eLen = this.stageData.ellipseBLen, pLen = this.stageData.pointBLen, bDebugLen = this.#systemSettings.gameOptions.debug.boundaries.drawObjectBoundaries ? bDebug.length : 0; if (len) this.#webGlEngine._drawLines(b, this.#systemSettings.gameOptions.debug.boundaries.boundariesColor, this.#systemSettings.gameOptions.debug.boundaries.boundariesWidth); this.renderLoopDebug.incrementDrawCallsCounter(); if (eLen) { //draw ellipse boundaries for (let i = 0; i &lt; eLen; i+=4) { const x = eB[i], y = eB[i+1], radX = eB[i+2], radY = eB[i+3], vertices = utils.calculateEllipseVertices(x, y, radX, radY); this.#webGlEngine._drawPolygon({x: 0, y: 0, vertices, isOffsetTurnedOff: true}, this.stageData); this.renderLoopDebug.incrementDrawCallsCounter(); //this.#webGlEngine._drawLines(vertices, this.systemSettings.gameOptions.debug.boundaries.boundariesColor, this.systemSettings.gameOptions.debug.boundaries.boundariesWidth); } } if (pLen) { //draw point boundaries for (let i = 0; i &lt; pLen; i+=2) { const x = pB[i], y = pB[i+1], vertices = [x,y, x+1,y+1]; this.#webGlEngine._drawLines(vertices, this.#systemSettings.gameOptions.debug.boundaries.boundariesColor, this.#systemSettings.gameOptions.debug.boundaries.boundariesWidth); this.renderLoopDebug.incrementDrawCallsCounter(); } } if (bDebugLen > 0) { this.#webGlEngine._drawLines(bDebug, this.#systemSettings.gameOptions.debug.boundaries.boundariesColor, this.#systemSettings.gameOptions.debug.boundaries.boundariesWidth); } resolve(); }); } /** * * @param {DrawTiledLayer} renderLayer * @returns {Promise&lt;void>} */ #layerBoundariesPrecalculation(renderLayer) { return new Promise((resolve, reject) => { /* if (renderLayer.setBoundaries) { const tilemap = this.#iLoader.getTileMap(renderLayer.tileMapKey), tilesets = tilemap.tilesets, layerData = tilemap.layers.find((layer) => layer.name === renderLayer.layerKey), { tileheight:dtheight, tilewidth:dtwidth } = tilemap, tilewidth = dtwidth, tileheight = dtheight, [ settingsWorldWidth, settingsWorldHeight ] = this.stageData.worldDimensions; let boundaries = []; if (!layerData) { Warning(WARNING_CODES.NOT_FOUND, "check tilemap and layers name"); reject(); } for (let i = 0; i &lt; tilesets.length; i++) { const layerCols = layerData.width, layerRows = layerData.height, worldW = tilewidth * layerCols, worldH = tileheight * layerRows; if (worldW !== settingsWorldWidth || worldH !== settingsWorldHeight) { Warning(WARNING_CODES.UNEXPECTED_WORLD_SIZE, " World size from tilemap is different than settings one, fixing..."); this.stageData._setWorldDimensions(worldW, worldH); } if (renderLayer.setBoundaries &amp;&amp; this.#systemSettings.gameOptions.render.boundaries.mapBoundariesEnabled) { this.stageData._setWholeWorldMapBoundaries(); } //calculate boundaries let mapIndex = 0; for (let row = 0; row &lt; layerRows; row++) { for (let col = 0; col &lt; layerCols; col++) { let tile = layerData.data[mapIndex], mapPosX = col * tilewidth, mapPosY = row * tileheight; if (tile !== 0) { tile -= 1; boundaries.push([mapPosX, mapPosY, mapPosX + tilewidth, mapPosY]); boundaries.push([mapPosX + tilewidth, mapPosY, mapPosX + tilewidth, mapPosY + tileheight]); boundaries.push([mapPosX + tilewidth, mapPosY + tileheight, mapPosX, mapPosY + tileheight]); boundaries.push([mapPosX, mapPosY + tileheight, mapPosX, mapPosY ]); } mapIndex++; } } } this.stageData._setWholeMapBoundaries(boundaries); this.stageData._mergeBoundaries(true); resolve(); } else { resolve(); }*/ }); } #countFPSaverage() { const timeLeft = this.#systemSettings.gameOptions.render.cyclesTimeCalc.averageFPStime, steps = this.renderLoopDebug.tempRCircleTPointer; let fullTime = 0; for (let i = 0; i &lt; steps; i++) { const timeStep = this.renderLoopDebug.tempRCircleT[i]; fullTime += timeStep; } console.log("FPS average for", timeLeft/1000, "sec, is ", (1000 / (fullTime / steps)).toFixed(2)); console.log("Last loop info:"); console.log("Webgl draw calls: ", this.renderLoopDebug.drawCalls); console.log("Vertices draw: ", this.renderLoopDebug.verticesDraw); // cleanup this.renderLoopDebug.cleanupTempVars(); } }</code></pre> </article> </section> </div> <nav> <div class="switcher"><ul><li class="active"><a href="/">1.5.9</a></li></div> <h2><a href="/">Home</a></h2><h3>Classes</h3><ul><li><a href="DrawCircleObject.html">DrawCircleObject</a></li><li><a href="DrawConusObject.html">DrawConusObject</a></li><li><a href="DrawImageObject.html">DrawImageObject</a></li><li><a href="DrawLineObject.html">DrawLineObject</a></li><li><a href="DrawObjectFactory.html">DrawObjectFactory</a></li><li><a href="DrawPolygonObject.html">DrawPolygonObject</a></li><li><a href="DrawRectObject.html">DrawRectObject</a></li><li><a href="DrawShapeObject.html">DrawShapeObject</a></li><li><a href="DrawTextObject.html">DrawTextObject</a></li><li><a href="DrawTiledLayer.html">DrawTiledLayer</a></li><li><a href="GameStage.html">GameStage</a></li><li><a href="GameStageData.html">GameStageData</a></li><li><a href="IExtension.html">IExtension</a></li><li><a href="INetwork.html">INetwork</a></li><li><a href="IRender.html">IRender</a></li><li><a href="ISystem.html">ISystem</a></li><li><a href="ISystemAudio.html">ISystemAudio</a></li><li><a href="RenderLoop.html">RenderLoop</a></li><li><a href="RenderLoopDebug.html">RenderLoopDebug</a></li><li><a href="System.html">System</a></li><li><a href="SystemSettings.html">SystemSettings</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-application_scheme.html">Application Scheme</a></li><li><a href="tutorial-assets_manager.html">Assets Manager</a></li><li><a href="tutorial-common_issues.html">Common Issues</a></li><li><a href="tutorial-how_to_add_and_use_audio.html">How to add and use audio</a></li><li><a href="tutorial-how_to_do_animations.html">How to Create Animations</a></li><li><a href="tutorial-how_to_load_and_use_tilemaps.html">How to Load and Use Tilemaps</a></li><li><a href="tutorial-quick_start.html">Quick Start</a></li><li><a href="tutorial-spine_animations.html">How to Add Spine Animations</a></li><li><a href="tutorial-stages_lifecycle.html">Stages Lifecycle</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 18 2025 06:53:54 GMT+0000 (Coordinated Universal Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>