| // Copyright (C) 2024 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {assertUnreachable} from './logging'; |
| import {Time, time} from './time'; |
| |
| export type RoundMode = 'round' | 'floor' | 'ceil'; |
| |
| /** |
| * Represents a time value in trace processor's time units, which is capable of |
| * representing a time with at least 64 bit integer precision and 53 bits of |
| * fractional precision. |
| * |
| * This class is immutable - any methods that modify this time will return a new |
| * copy containing instead. |
| */ |
| export class HighPrecisionTime { |
| // This is the high precision time representing 0 |
| static readonly ZERO = new HighPrecisionTime(Time.fromRaw(0n)); |
| |
| // time value == |integral| + |fractional| |
| // |fractional| is kept in the range 0 <= x < 1 to avoid losing precision |
| readonly integral: time; |
| readonly fractional: number; |
| |
| /** |
| * Constructs a HighPrecisionTime object. |
| * |
| * @param integral The integer part of the time value. |
| * @param fractional The fractional part of the time value. |
| */ |
| constructor(integral: time, fractional: number = 0) { |
| // Normalize |fractional| to the range 0.0 <= x < 1.0 |
| const fractionalFloor = Math.floor(fractional); |
| this.integral = (integral + BigInt(fractionalFloor)) as time; |
| this.fractional = fractional - fractionalFloor; |
| } |
| |
| /** |
| * Converts to an integer time value. |
| * |
| * @param round How to round ('round', 'floor', or 'ceil'). |
| */ |
| toTime(round: RoundMode = 'floor'): time { |
| switch (round) { |
| case 'round': |
| return Time.fromRaw( |
| this.integral + BigInt(Math.round(this.fractional)), |
| ); |
| case 'floor': |
| return Time.fromRaw(this.integral); |
| case 'ceil': |
| return Time.fromRaw(this.integral + BigInt(Math.ceil(this.fractional))); |
| default: |
| assertUnreachable(round); |
| } |
| } |
| |
| /** |
| * Converts to a JavaScript number. Precision loss should be expected when |
| * integral values are large. |
| */ |
| toNumber(): number { |
| return Number(this.integral) + this.fractional; |
| } |
| |
| /** |
| * Adds another HighPrecisionTime to this one and returns the result. |
| * |
| * @param time A HighPrecisionTime object to add. |
| */ |
| add(time: HighPrecisionTime): HighPrecisionTime { |
| return new HighPrecisionTime( |
| Time.add(this.integral, time.integral), |
| this.fractional + time.fractional, |
| ); |
| } |
| |
| /** |
| * Adds an integer time value to this HighPrecisionTime and returns the result. |
| * |
| * @param t A time value to add. |
| */ |
| addTime(t: time): HighPrecisionTime { |
| return new HighPrecisionTime(Time.add(this.integral, t), this.fractional); |
| } |
| |
| /** |
| * Adds a floating point time value to this one and returns the result. |
| * |
| * @param n A floating point value to add. |
| */ |
| addNumber(n: number): HighPrecisionTime { |
| return new HighPrecisionTime(this.integral, this.fractional + n); |
| } |
| |
| /** |
| * Subtracts another HighPrecisionTime from this one and returns the result. |
| * |
| * @param time A HighPrecisionTime object to subtract. |
| */ |
| sub(time: HighPrecisionTime): HighPrecisionTime { |
| return new HighPrecisionTime( |
| Time.sub(this.integral, time.integral), |
| this.fractional - time.fractional, |
| ); |
| } |
| |
| /** |
| * Subtract an integer time value from this HighPrecisionTime and returns the |
| * result. |
| * |
| * @param t A time value to subtract. |
| */ |
| subTime(t: time): HighPrecisionTime { |
| return new HighPrecisionTime(Time.sub(this.integral, t), this.fractional); |
| } |
| |
| /** |
| * Subtracts a floating point time value from this one and returns the result. |
| * |
| * @param n A floating point value to subtract. |
| */ |
| subNumber(n: number): HighPrecisionTime { |
| return new HighPrecisionTime(this.integral, this.fractional - n); |
| } |
| |
| /** |
| * Checks if this HighPrecisionTime is approximately equal to another, within |
| * a given epsilon. |
| * |
| * @param other A HighPrecisionTime object to compare. |
| * @param epsilon The tolerance for equality check. |
| */ |
| equals(other: HighPrecisionTime, epsilon: number = 1e-6): boolean { |
| return Math.abs(this.sub(other).toNumber()) < epsilon; |
| } |
| |
| /** |
| * Checks if this time value is within the range defined by [start, end). |
| * |
| * @param start The start of the time range (inclusive). |
| * @param end The end of the time range (exclusive). |
| */ |
| containedWithin(start: time, end: time): boolean { |
| return this.integral >= start && this.integral < end; |
| } |
| |
| /** |
| * Checks if this HighPrecisionTime is less than a given time. |
| * |
| * @param t A time value. |
| */ |
| lt(t: time): boolean { |
| return this.integral < t; |
| } |
| |
| /** |
| * Checks if this HighPrecisionTime is less than or equal to a given time. |
| * |
| * @param t A time value. |
| */ |
| lte(t: time): boolean { |
| return ( |
| this.integral < t || |
| (this.integral === t && Math.abs(this.fractional - 0.0) < Number.EPSILON) |
| ); |
| } |
| |
| /** |
| * Checks if this HighPrecisionTime is greater than a given time. |
| * |
| * @param t A time value. |
| */ |
| gt(t: time): boolean { |
| return ( |
| this.integral > t || |
| (this.integral === t && Math.abs(this.fractional - 0.0) > Number.EPSILON) |
| ); |
| } |
| |
| /** |
| * Checks if this HighPrecisionTime is greater than or equal to a given time. |
| * |
| * @param t A time value. |
| */ |
| gte(t: time): boolean { |
| return this.integral >= t; |
| } |
| |
| /** |
| * Clamps this HighPrecisionTime to be within the specified range. |
| * |
| * @param lower The lower bound of the range. |
| * @param upper The upper bound of the range. |
| */ |
| clamp(lower: time, upper: time): HighPrecisionTime { |
| if (this.integral < lower) { |
| return new HighPrecisionTime(lower); |
| } else if (this.integral >= upper) { |
| return new HighPrecisionTime(upper); |
| } else { |
| return this; |
| } |
| } |
| |
| /** |
| * Returns the absolute value of this HighPrecisionTime. |
| */ |
| abs(): HighPrecisionTime { |
| if (this.integral >= 0n) { |
| return this; |
| } |
| const newIntegral = Time.fromRaw(-this.integral); |
| const newFractional = -this.fractional; |
| return new HighPrecisionTime(newIntegral, newFractional); |
| } |
| |
| /** |
| * Converts this HighPrecisionTime to a string representation. |
| */ |
| toString(): string { |
| const fractionalAsString = this.fractional.toString(); |
| if (fractionalAsString === '0') { |
| return this.integral.toString(); |
| } else { |
| return `${this.integral}${fractionalAsString.substring(1)}`; |
| } |
| } |
| } |