mirror of
				https://github.com/tobspr/shapez.io.git
				synced 2025-06-13 13:04:03 +00:00 
			
		
		
		
	Support dynamic tick rates
This commit is contained in:
		
							parent
							
								
									c4aa2f7d61
								
							
						
					
					
						commit
						80a90298c1
					
				
							
								
								
									
										10
									
								
								src/css/ingame_hud/debug_info.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/css/ingame_hud/debug_info.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| #ingame_HUD_DebugInfo { | ||||
|     position: absolute; | ||||
|     @include S(bottom, 5px); | ||||
|     @include S(left, 5px); | ||||
| 
 | ||||
|     font-size: 15px; | ||||
|     display: flex; | ||||
|     line-height: 15px; | ||||
|     flex-direction: column; | ||||
| } | ||||
| @ -39,6 +39,7 @@ | ||||
| @import "ingame_hud/pinned_shapes"; | ||||
| @import "ingame_hud/notifications"; | ||||
| @import "ingame_hud/settings_menu"; | ||||
| @import "ingame_hud/debug_info"; | ||||
| 
 | ||||
| // prettier-ignore | ||||
| $elements:  | ||||
| @ -57,6 +58,7 @@ ingame_HUD_GameMenu, | ||||
| ingame_HUD_KeybindingOverlay, | ||||
| ingame_HUD_Notifications, | ||||
| ingame_HUD_MassSelector, | ||||
| ingame_HUD_DebugInfo, | ||||
| 
 | ||||
| // Overlays | ||||
| ingame_HUD_BetaOverlay, | ||||
|  | ||||
| @ -23,13 +23,8 @@ export const globalConfig = { | ||||
|     statisticsGraphSlices: 100, | ||||
|     analyticsSliceDurationSeconds: 10, | ||||
| 
 | ||||
|     // [Calculated] physics step size
 | ||||
|     physicsDeltaMs: 0, | ||||
|     physicsDeltaSeconds: 0, | ||||
| 
 | ||||
|     // Update physics at N fps, independent of rendering
 | ||||
|     // physicsUpdateRate: 55,
 | ||||
|     physicsUpdateRate: 120, | ||||
|     minimumTickRate: 30, | ||||
|     maximumTickRate: 500, | ||||
| 
 | ||||
|     // Map
 | ||||
|     mapChunkSize: 32, | ||||
| @ -76,7 +71,7 @@ export const globalConfig = { | ||||
| 
 | ||||
|     debug: { | ||||
|         /* dev:start */ | ||||
|         fastGameEnter: true, | ||||
|         // fastGameEnter: true,
 | ||||
|         noArtificialDelays: true, | ||||
|         // disableSavegameWrite: true,
 | ||||
|         showEntityBounds: false, | ||||
| @ -111,7 +106,4 @@ export const IS_MOBILE = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); | ||||
| 
 | ||||
| // Automatic calculations
 | ||||
| 
 | ||||
| globalConfig.physicsDeltaMs = 1000.0 / globalConfig.physicsUpdateRate; | ||||
| globalConfig.physicsDeltaSeconds = 1.0 / globalConfig.physicsUpdateRate; | ||||
| 
 | ||||
| globalConfig.minerSpeedItemsPerSecond = globalConfig.beltSpeedItemsPerSecond / 5; | ||||
|  | ||||
| @ -62,7 +62,7 @@ export class UndergroundBeltComponent extends Component { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         this.pendingItems.push([item, 1 / beltSpeed / globalConfig.itemSpacingOnBelts]); | ||||
|         this.pendingItems.push([item, 0]); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| @ -88,7 +88,8 @@ export class UndergroundBeltComponent extends Component { | ||||
|         // NOTICE:
 | ||||
|         // This corresponds to the item ejector - it needs 0.5 additional tiles to eject the item.
 | ||||
|         // So instead of adding 1 we add 0.5 only.
 | ||||
|         const travelDuration = (travelDistance + 0.5) / beltSpeed / globalConfig.itemSpacingOnBelts; | ||||
|         // Additionally it takes 1 tile for the acceptor which we just add on top.
 | ||||
|         const travelDuration = (travelDistance + 1.5) / beltSpeed / globalConfig.itemSpacingOnBelts; | ||||
| 
 | ||||
|         this.pendingItems.push([item, travelDuration]); | ||||
| 
 | ||||
|  | ||||
| @ -32,6 +32,7 @@ import { GameTime } from "./time/game_time"; | ||||
| import { ProductionAnalytics } from "./production_analytics"; | ||||
| import { randomInt } from "../core/utils"; | ||||
| import { defaultBuildingVariant } from "./meta_building"; | ||||
| import { DynamicTickrate } from "./dynamic_tickrate"; | ||||
| 
 | ||||
| const logger = createLogger("ingame/core"); | ||||
| 
 | ||||
| @ -53,16 +54,6 @@ export class GameCore { | ||||
|         /** @type {GameRoot} */ | ||||
|         this.root = null; | ||||
| 
 | ||||
|         /** | ||||
|          * Time budget (seconds) for logic updates | ||||
|          */ | ||||
|         this.logicTimeBudget = 0; | ||||
| 
 | ||||
|         /** | ||||
|          * Time budget (seconds) for user interface updates | ||||
|          */ | ||||
|         this.uiTimeBudget = 0; | ||||
| 
 | ||||
|         /** | ||||
|          * Set to true at the beginning of a logic update and cleared when its finished. | ||||
|          * This is to prevent doing a recursive logic update which can lead to unexpected | ||||
| @ -97,6 +88,9 @@ export class GameCore { | ||||
|         // This isn't nice, but we need it right here
 | ||||
|         root.gameState.keyActionMapper = new KeyActionMapper(root, this.root.gameState.inputReciever); | ||||
| 
 | ||||
|         // Needs to come first
 | ||||
|         root.dynamicTickrate = new DynamicTickrate(root); | ||||
| 
 | ||||
|         // Init classes
 | ||||
|         root.camera = new Camera(root); | ||||
|         root.map = new MapView(root); | ||||
| @ -250,17 +244,6 @@ export class GameCore { | ||||
|         // Perform logic ticks
 | ||||
|         this.root.time.performTicks(deltaMs, this.boundInternalTick); | ||||
| 
 | ||||
|         // Update UI particles
 | ||||
|         this.uiTimeBudget += deltaMs; | ||||
|         const maxUiSteps = 3; | ||||
|         if (this.uiTimeBudget > globalConfig.physicsDeltaMs * maxUiSteps) { | ||||
|             this.uiTimeBudget = globalConfig.physicsDeltaMs; | ||||
|         } | ||||
|         while (this.uiTimeBudget >= globalConfig.physicsDeltaMs) { | ||||
|             this.uiTimeBudget -= globalConfig.physicsDeltaMs; | ||||
|             // root.uiParticleMgr.update();
 | ||||
|         } | ||||
| 
 | ||||
|         // Update analytics
 | ||||
|         root.productionAnalytics.update(); | ||||
| 
 | ||||
| @ -288,6 +271,9 @@ export class GameCore { | ||||
| 
 | ||||
|     updateLogic() { | ||||
|         const root = this.root; | ||||
| 
 | ||||
|         root.dynamicTickrate.beginTick(); | ||||
| 
 | ||||
|         this.duringLogicUpdate = true; | ||||
| 
 | ||||
|         // Update entities, this removes destroyed entities
 | ||||
| @ -296,6 +282,8 @@ export class GameCore { | ||||
|         // IMPORTANT: At this point, the game might be game over. Stop if this is the case
 | ||||
|         if (!this.root) { | ||||
|             logger.log("Root destructed, returning false"); | ||||
|             root.dynamicTickrate.endTick(); | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
| @ -303,7 +291,7 @@ export class GameCore { | ||||
|         // root.particleMgr.update();
 | ||||
| 
 | ||||
|         this.duringLogicUpdate = false; | ||||
| 
 | ||||
|         root.dynamicTickrate.endTick(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										104
									
								
								src/js/game/dynamic_tickrate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/js/game/dynamic_tickrate.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | ||||
| import { GameRoot } from "./root"; | ||||
| import { createLogger } from "../core/logging"; | ||||
| import { globalConfig } from "../core/config"; | ||||
| import { performanceNow, Math_min, Math_round, Math_max } from "../core/builtins"; | ||||
| import { round3Digits } from "../core/utils"; | ||||
| 
 | ||||
| const logger = createLogger("dynamic_tickrate"); | ||||
| 
 | ||||
| export class DynamicTickrate { | ||||
|     /** | ||||
|      * | ||||
|      * @param {GameRoot} root | ||||
|      */ | ||||
|     constructor(root) { | ||||
|         this.root = root; | ||||
| 
 | ||||
|         this.setTickRate(120); | ||||
| 
 | ||||
|         this.currentTickStart = null; | ||||
|         this.capturedTicks = []; | ||||
|         this.averageTickDuration = 0; | ||||
| 
 | ||||
|         // Exposed
 | ||||
|         this.deltaSeconds = 0; | ||||
|         this.deltaMs = 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the tick rate to N updates per second | ||||
|      * @param {number} rate | ||||
|      */ | ||||
|     setTickRate(rate) { | ||||
|         logger.log("Applying tick-rate of", rate); | ||||
|         this.currentTickRate = rate; | ||||
|         this.deltaMs = 1000.0 / this.currentTickRate; | ||||
|         this.deltaSeconds = 1.0 / this.currentTickRate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Increases the tick rate marginally | ||||
|      */ | ||||
|     increaseTickRate() { | ||||
|         this.setTickRate(Math_round(Math_min(globalConfig.maximumTickRate, this.currentTickRate * 1.1))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Decreases the tick rate marginally | ||||
|      */ | ||||
|     decreaseTickRate() { | ||||
|         this.setTickRate(Math_round(Math_min(globalConfig.maximumTickRate, this.currentTickRate * 0.9))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call whenever a tick began | ||||
|      */ | ||||
|     beginTick() { | ||||
|         assert(this.currentTickStart === null, "BeginTick called twice"); | ||||
|         this.currentTickStart = performanceNow(); | ||||
| 
 | ||||
|         if (this.capturedTicks.length > this.currentTickRate * 4) { | ||||
|             // Take only a portion of the ticks
 | ||||
|             this.capturedTicks.sort(); | ||||
|             this.capturedTicks.splice(0, 10); | ||||
|             this.capturedTicks.splice(this.capturedTicks.length - 11, 10); | ||||
| 
 | ||||
|             let average = 0; | ||||
|             for (let i = 0; i < this.capturedTicks.length; ++i) { | ||||
|                 average += this.capturedTicks[i]; | ||||
|             } | ||||
|             average /= this.capturedTicks.length; | ||||
| 
 | ||||
|             // Calculate tick duration to cover X% of the frame
 | ||||
|             const ticksPerFrame = this.currentTickRate / 60; | ||||
|             const maxFrameDurationMs = 8; | ||||
|             const maxTickDuration = maxFrameDurationMs / ticksPerFrame; | ||||
|             // const maxTickDuration = (1000 / this.currentTickRate) * 0.75;
 | ||||
|             logger.log( | ||||
|                 "Average time per tick:", | ||||
|                 round3Digits(average) + "ms", | ||||
|                 "allowed are", | ||||
|                 maxTickDuration | ||||
|             ); | ||||
|             this.averageTickDuration = average; | ||||
| 
 | ||||
|             if (average < maxTickDuration) { | ||||
|                 this.increaseTickRate(); | ||||
|             } else { | ||||
|                 this.decreaseTickRate(); | ||||
|             } | ||||
| 
 | ||||
|             this.capturedTicks = []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call whenever a tick ended | ||||
|      */ | ||||
|     endTick() { | ||||
|         assert(this.currentTickStart !== null, "EndTick called without BeginTick"); | ||||
|         const duration = performanceNow() - this.currentTickStart; | ||||
|         this.capturedTicks.push(duration); | ||||
|         this.currentTickStart = null; | ||||
|     } | ||||
| } | ||||
| @ -21,6 +21,7 @@ import { HUDPinnedShapes } from "./parts/pinned_shapes"; | ||||
| import { ShapeDefinition } from "../shape_definition"; | ||||
| import { HUDNotifications, enumNotificationType } from "./parts/notifications"; | ||||
| import { HUDSettingsMenu } from "./parts/settings_menu"; | ||||
| import { HUDDebugInfo } from "./parts/debug_info"; | ||||
| 
 | ||||
| export class GameHUD { | ||||
|     /** | ||||
| @ -57,6 +58,7 @@ export class GameHUD { | ||||
|             settingsMenu: new HUDSettingsMenu(this.root), | ||||
| 
 | ||||
|             // betaOverlay: new HUDBetaOverlay(this.root),
 | ||||
|             debugInfo: new HUDDebugInfo(this.root), | ||||
|         }; | ||||
| 
 | ||||
|         this.signals = { | ||||
|  | ||||
							
								
								
									
										19
									
								
								src/js/game/hud/parts/debug_info.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/js/game/hud/parts/debug_info.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| import { BaseHUDPart } from "../base_hud_part"; | ||||
| import { makeDiv, round3Digits } from "../../../core/utils"; | ||||
| 
 | ||||
| export class HUDDebugInfo extends BaseHUDPart { | ||||
|     createElements(parent) { | ||||
|         this.element = makeDiv(parent, "ingame_HUD_DebugInfo", []); | ||||
| 
 | ||||
|         this.tickRateElement = makeDiv(this.element, null, ["tickRate"], "Ticks /s: 120"); | ||||
|         this.tickDurationElement = makeDiv(this.element, null, ["tickDuration"], "Update time: 0.5ms"); | ||||
|     } | ||||
| 
 | ||||
|     initialize() {} | ||||
| 
 | ||||
|     update() { | ||||
|         this.tickRateElement.innerText = "Tickrate: " + this.root.dynamicTickrate.currentTickRate; | ||||
|         this.tickDurationElement.innerText = | ||||
|             "Avg. Dur: " + round3Digits(this.root.dynamicTickrate.averageTickDuration) + "ms"; | ||||
|     } | ||||
| } | ||||
| @ -26,6 +26,7 @@ import { ProductionAnalytics } from "./production_analytics"; | ||||
| import { Entity } from "./entity"; | ||||
| import { ShapeDefinition } from "./shape_definition"; | ||||
| import { BaseItem } from "./base_item"; | ||||
| import { DynamicTickrate } from "./dynamic_tickrate"; | ||||
| /* typehints:end */ | ||||
| 
 | ||||
| const logger = createLogger("game/root"); | ||||
| @ -115,6 +116,9 @@ export class GameRoot { | ||||
|         /** @type {ProductionAnalytics} */ | ||||
|         this.productionAnalytics = null; | ||||
| 
 | ||||
|         /** @type {DynamicTickrate} */ | ||||
|         this.dynamicTickrate = null; | ||||
| 
 | ||||
|         this.signals = { | ||||
|             // Entities
 | ||||
|             entityAdded: /** @type {TypedSignal<[Entity]>} */ (new Signal()), | ||||
|  | ||||
| @ -109,78 +109,98 @@ export class BeltSystem extends GameSystemWithFilter { | ||||
|         this.forEachMatchingEntityOnScreen(parameters, this.drawEntityItems.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     update() { | ||||
|     /** | ||||
|      * Updates a given entity | ||||
|      * @param {Entity} entity | ||||
|      * @param {Set} processedEntities | ||||
|      */ | ||||
|     updateBelt(entity, processedEntities) { | ||||
|         if (processedEntities.has(entity.uid)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         processedEntities.add(entity.uid); | ||||
| 
 | ||||
|         // Divide by item spacing on belts since we use throughput and not speed
 | ||||
|         const beltSpeed = | ||||
|             this.root.hubGoals.getBeltBaseSpeed() * | ||||
|             globalConfig.physicsDeltaSeconds * | ||||
|             this.root.dynamicTickrate.deltaSeconds * | ||||
|             globalConfig.itemSpacingOnBelts; | ||||
|         const beltComp = entity.components.Belt; | ||||
|         const staticComp = entity.components.StaticMapEntity; | ||||
|         const items = beltComp.sortedItems; | ||||
| 
 | ||||
|         if (items.length === 0) { | ||||
|             // Fast out for performance
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const ejectorComp = entity.components.ItemEjector; | ||||
|         let maxProgress = 1; | ||||
| 
 | ||||
|         // When ejecting, we can not go further than the item spacing since it
 | ||||
|         // will be on the corner
 | ||||
|         if (ejectorComp.isAnySlotEjecting()) { | ||||
|             maxProgress = 1 - globalConfig.itemSpacingOnBelts; | ||||
|         } else { | ||||
|             // Find follow up belt to make sure we don't clash items
 | ||||
|             const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction); | ||||
|             const followUpVector = enumDirectionToVector[followUpDirection]; | ||||
| 
 | ||||
|             const followUpTile = staticComp.origin.add(followUpVector); | ||||
|             const followUpEntity = this.root.map.getTileContent(followUpTile); | ||||
| 
 | ||||
|             if (followUpEntity) { | ||||
|                 const followUpBeltComp = followUpEntity.components.Belt; | ||||
|                 if (followUpBeltComp) { | ||||
|                     // Update follow up belt first
 | ||||
|                     this.updateBelt(followUpEntity, processedEntities); | ||||
| 
 | ||||
|                     const spacingOnBelt = followUpBeltComp.getDistanceToFirstItemCenter(); | ||||
|                     maxProgress = Math_min(1, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let speedMultiplier = 1; | ||||
|         if (beltComp.direction !== enumDirection.top) { | ||||
|             // Shaped belts are longer, thus being quicker
 | ||||
|             speedMultiplier = 1.41; | ||||
|         } | ||||
| 
 | ||||
|         for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) { | ||||
|             const itemAndProgress = items[itemIndex]; | ||||
| 
 | ||||
|             const newProgress = itemAndProgress[0] + speedMultiplier * beltSpeed; | ||||
|             if (newProgress >= 1.0) { | ||||
|                 // Try to give this item to a new belt
 | ||||
|                 const freeSlot = ejectorComp.getFirstFreeSlot(); | ||||
| 
 | ||||
|                 if (freeSlot === null) { | ||||
|                     // So, we don't have a free slot - damned!
 | ||||
|                     itemAndProgress[0] = 1.0; | ||||
|                     maxProgress = 1 - globalConfig.itemSpacingOnBelts; | ||||
|                 } else { | ||||
|                     // We got a free slot, remove this item and keep it on the ejector slot
 | ||||
|                     if (!ejectorComp.tryEject(freeSlot, itemAndProgress[1])) { | ||||
|                         assert(false, "Ejection failed"); | ||||
|                     } | ||||
|                     items.splice(itemIndex, 1); | ||||
|                     maxProgress = 1; | ||||
|                 } | ||||
|             } else { | ||||
|                 itemAndProgress[0] = Math_min(newProgress, maxProgress); | ||||
|                 maxProgress = itemAndProgress[0] - globalConfig.itemSpacingOnBelts; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     update() { | ||||
|         const processedEntities = new Set(); | ||||
| 
 | ||||
|         for (let i = 0; i < this.allEntities.length; ++i) { | ||||
|             const entity = this.allEntities[i]; | ||||
|             const beltComp = entity.components.Belt; | ||||
|             const staticComp = entity.components.StaticMapEntity; | ||||
|             const items = beltComp.sortedItems; | ||||
|             if (items.length === 0) { | ||||
|                 // Fast out for performance
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             const ejectorComp = entity.components.ItemEjector; | ||||
|             let maxProgress = 1; | ||||
| 
 | ||||
|             // When ejecting, we can not go further than the item spacing since it
 | ||||
|             // will be on the corner
 | ||||
|             if (ejectorComp.isAnySlotEjecting()) { | ||||
|                 maxProgress = 1 - globalConfig.itemSpacingOnBelts; | ||||
|             } else { | ||||
|                 // Find follow up belt to make sure we don't clash items
 | ||||
|                 const followUpDirection = staticComp.localDirectionToWorld(beltComp.direction); | ||||
|                 const followUpVector = enumDirectionToVector[followUpDirection]; | ||||
| 
 | ||||
|                 const followUpTile = staticComp.origin.add(followUpVector); | ||||
|                 const followUpEntity = this.root.map.getTileContent(followUpTile); | ||||
| 
 | ||||
|                 if (followUpEntity) { | ||||
|                     const followUpBeltComp = followUpEntity.components.Belt; | ||||
|                     if (followUpBeltComp) { | ||||
|                         const spacingOnBelt = followUpBeltComp.getDistanceToFirstItemCenter(); | ||||
|                         maxProgress = Math_min(1, 1 - globalConfig.itemSpacingOnBelts + spacingOnBelt); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             let speedMultiplier = 1; | ||||
|             if (beltComp.direction !== enumDirection.top) { | ||||
|                 // Shaped belts are longer, thus being quicker
 | ||||
|                 speedMultiplier = 1.41; | ||||
|             } | ||||
| 
 | ||||
|             for (let itemIndex = items.length - 1; itemIndex >= 0; --itemIndex) { | ||||
|                 const itemAndProgress = items[itemIndex]; | ||||
| 
 | ||||
|                 const newProgress = itemAndProgress[0] + speedMultiplier * beltSpeed; | ||||
|                 if (newProgress >= 1.0) { | ||||
|                     // Try to give this item to a new belt
 | ||||
|                     const freeSlot = ejectorComp.getFirstFreeSlot(); | ||||
| 
 | ||||
|                     if (freeSlot === null) { | ||||
|                         // So, we don't have a free slot - damned!
 | ||||
|                         itemAndProgress[0] = 1.0; | ||||
|                         maxProgress = 1 - globalConfig.itemSpacingOnBelts; | ||||
|                     } else { | ||||
|                         // We got a free slot, remove this item and keep it on the ejector slot
 | ||||
|                         if (!ejectorComp.tryEject(freeSlot, itemAndProgress[1])) { | ||||
|                             assert(false, "Ejection failed"); | ||||
|                         } | ||||
|                         items.splice(itemIndex, 1); | ||||
|                         maxProgress = 1; | ||||
|                     } | ||||
|                 } else { | ||||
|                     itemAndProgress[0] = Math_min(newProgress, maxProgress); | ||||
|                     maxProgress = itemAndProgress[0] - globalConfig.itemSpacingOnBelts; | ||||
|                 } | ||||
|             } | ||||
|             this.updateBelt(entity, processedEntities); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -31,7 +31,7 @@ export class ItemAcceptorSystem extends GameSystemWithFilter { | ||||
|             for (let animIndex = 0; animIndex < aceptorComp.itemConsumptionAnimations.length; ++animIndex) { | ||||
|                 const anim = aceptorComp.itemConsumptionAnimations[animIndex]; | ||||
|                 anim.animProgress += | ||||
|                     globalConfig.physicsDeltaSeconds * | ||||
|                     this.root.dynamicTickrate.deltaSeconds * | ||||
|                     this.root.hubGoals.getBeltBaseSpeed() * | ||||
|                     2 * | ||||
|                     globalConfig.itemSpacingOnBelts; | ||||
|  | ||||
| @ -14,7 +14,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter { | ||||
| 
 | ||||
|     update() { | ||||
|         const effectiveBeltSpeed = this.root.hubGoals.getBeltBaseSpeed() * globalConfig.itemSpacingOnBelts; | ||||
|         const progressGrowth = (effectiveBeltSpeed / 0.5) * globalConfig.physicsDeltaSeconds; | ||||
|         const progressGrowth = (effectiveBeltSpeed / 0.5) * this.root.dynamicTickrate.deltaSeconds; | ||||
| 
 | ||||
|         // Try to find acceptors for every ejector
 | ||||
|         for (let i = 0; i < this.allEntities.length; ++i) { | ||||
|  | ||||
| @ -23,7 +23,7 @@ export class ItemProcessorSystem extends GameSystemWithFilter { | ||||
|             // First of all, process the current recipe
 | ||||
|             processorComp.secondsUntilEject = Math_max( | ||||
|                 0, | ||||
|                 processorComp.secondsUntilEject - globalConfig.physicsDeltaSeconds | ||||
|                 processorComp.secondsUntilEject - this.root.dynamicTickrate.deltaSeconds | ||||
|             ); | ||||
| 
 | ||||
|             // Check if we have any finished items we can eject
 | ||||
|  | ||||
| @ -31,7 +31,7 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { | ||||
|             // Decrease remaining time of all items in belt
 | ||||
|             for (let k = 0; k < undergroundComp.pendingItems.length; ++k) { | ||||
|                 const item = undergroundComp.pendingItems[k]; | ||||
|                 item[1] = Math_max(0, item[1] - globalConfig.physicsDeltaSeconds); | ||||
|                 item[1] = Math_max(0, item[1] - this.root.dynamicTickrate.deltaSeconds); | ||||
|             } | ||||
| 
 | ||||
|             if (undergroundComp.mode === enumUndergroundBeltMode.sender) { | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { types, BasicSerializableObject } from "../../savegame/serialization"; | ||||
| import { RegularGameSpeed } from "./regular_game_speed"; | ||||
| import { BaseGameSpeed } from "./base_game_speed"; | ||||
| import { PausedGameSpeed } from "./paused_game_speed"; | ||||
| import { performanceNow } from "../../core/builtins"; | ||||
| import { performanceNow, Math_max } from "../../core/builtins"; | ||||
| import { FastForwardGameSpeed } from "./fast_forward_game_speed"; | ||||
| import { gGameSpeedRegistry } from "../../core/global_registries"; | ||||
| import { globalConfig } from "../../core/config"; | ||||
| @ -102,7 +102,7 @@ export class GameTime extends BasicSerializableObject { | ||||
|      * Internal method to generate new logic time budget | ||||
|      * @param {number} deltaMs | ||||
|      */ | ||||
|     înternalAddDeltaToBudget(deltaMs) { | ||||
|     internalAddDeltaToBudget(deltaMs) { | ||||
|         // Only update if game is supposed to update
 | ||||
|         if (this.root.hud.shouldPauseGame()) { | ||||
|             this.logicTimeBudget = 0; | ||||
| @ -112,9 +112,13 @@ export class GameTime extends BasicSerializableObject { | ||||
|         } | ||||
| 
 | ||||
|         // Check for too big pile of updates -> reduce it to 1
 | ||||
|         const maxLogicSteps = this.speed.getMaxLogicStepsInQueue(); | ||||
|         if (this.logicTimeBudget > globalConfig.physicsDeltaMs * maxLogicSteps) { | ||||
|             this.logicTimeBudget = globalConfig.physicsDeltaMs * maxLogicSteps; | ||||
|         const maxLogicSteps = Math_max( | ||||
|             3, | ||||
|             (this.speed.getMaxLogicStepsInQueue() * this.root.dynamicTickrate.currentTickRate) / 60 | ||||
|         ); | ||||
|         if (this.logicTimeBudget > this.root.dynamicTickrate.deltaMs * maxLogicSteps) { | ||||
|             // logger.warn("Skipping logic time steps since more than", maxLogicSteps, "are in queue");
 | ||||
|             this.logicTimeBudget = this.root.dynamicTickrate.deltaMs * maxLogicSteps; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -124,13 +128,13 @@ export class GameTime extends BasicSerializableObject { | ||||
|      * @param {function():boolean} updateMethod | ||||
|      */ | ||||
|     performTicks(deltaMs, updateMethod) { | ||||
|         this.înternalAddDeltaToBudget(deltaMs); | ||||
|         this.internalAddDeltaToBudget(deltaMs); | ||||
| 
 | ||||
|         const speedAtStart = this.root.time.getSpeed(); | ||||
| 
 | ||||
|         // Update physics & logic
 | ||||
|         while (this.logicTimeBudget >= globalConfig.physicsDeltaMs) { | ||||
|             this.logicTimeBudget -= globalConfig.physicsDeltaMs; | ||||
|         while (this.logicTimeBudget >= this.root.dynamicTickrate.deltaMs) { | ||||
|             this.logicTimeBudget -= this.root.dynamicTickrate.deltaMs; | ||||
| 
 | ||||
|             if (!updateMethod()) { | ||||
|                 // Gameover happened or so, do not update anymore
 | ||||
| @ -138,7 +142,7 @@ export class GameTime extends BasicSerializableObject { | ||||
|             } | ||||
| 
 | ||||
|             // Step game time
 | ||||
|             this.timeSeconds = quantizeFloat(this.timeSeconds + globalConfig.physicsDeltaSeconds); | ||||
|             this.timeSeconds = quantizeFloat(this.timeSeconds + this.root.dynamicTickrate.deltaSeconds); | ||||
| 
 | ||||
|             // Game time speed changed, need to abort since our logic steps are no longer valid
 | ||||
|             if (speedAtStart.getId() !== this.speed.getId()) { | ||||
|  | ||||
| @ -179,7 +179,12 @@ export class MainMenuState extends GameState { | ||||
|         this.trackClicks(qs(".mainContainer .importButton"), this.requestImportSavegame); | ||||
| 
 | ||||
|         if (G_IS_DEV && globalConfig.debug.fastGameEnter) { | ||||
|             this.onPlayButtonClicked(); | ||||
|             const games = this.app.savegameMgr.getSavegamesMetaData(); | ||||
|             if (games.length > 0) { | ||||
|                 this.resumeGame(games[0]); | ||||
|             } else { | ||||
|                 this.onPlayButtonClicked(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Initialize video
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 tobspr
						tobspr