From 0f0c343a853e9387117405e44036ed2c879ff12d Mon Sep 17 00:00:00 2001 From: dtookey Date: Tue, 15 Aug 2023 08:07:08 -0400 Subject: [PATCH] we have over-engineered "getrandom" to death --- .../kotlin/controllers/InputController.kt | 5 +- .../kotlin/controllers/TemporalController.kt | 2 +- .../controllers/windows/WindowsOSProxy.kt | 22 ++-- src/main/kotlin/util/HelperFunctions.kt | 23 +++- src/test/kotlin/util/HelperFunctionsTest.kt | 119 +++++++++++++++++- 5 files changed, 150 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/controllers/InputController.kt b/src/main/kotlin/controllers/InputController.kt index d7502e5..a3f436d 100644 --- a/src/main/kotlin/controllers/InputController.kt +++ b/src/main/kotlin/controllers/InputController.kt @@ -116,9 +116,10 @@ interface InputController { /** * Holds x and y variance bounds for random point generation. * - * Used as parameter in [MousePointerObserver.getNearbyPoint] to control the allowed offset range. + * Used as parameter in [InputController.getNearbyPoint] to control the allowed offset range. * - * @see MousePointerObserver.getNearbyPoint() + * @see InputController.getNearbyPoint + * @see InputController.animateMoveMouse */ data class PointerVarianceBounds( val xVariance: Int = 25, diff --git a/src/main/kotlin/controllers/TemporalController.kt b/src/main/kotlin/controllers/TemporalController.kt index 453f4c8..04dec0a 100644 --- a/src/main/kotlin/controllers/TemporalController.kt +++ b/src/main/kotlin/controllers/TemporalController.kt @@ -29,7 +29,7 @@ interface TemporalController { val duration = if(maxAdditionalDuration == 0L){ baseDuration }else{ - baseDuration + util.HelperFunctions.getRandomLongFromNormalDistribution(maxAdditionalDuration) + baseDuration + util.HelperFunctions.getGaussianLong(maxAdditionalDuration) } TimeUnit.MILLISECONDS.sleep(duration) diff --git a/src/main/kotlin/controllers/windows/WindowsOSProxy.kt b/src/main/kotlin/controllers/windows/WindowsOSProxy.kt index 8d7f3b2..012874b 100644 --- a/src/main/kotlin/controllers/windows/WindowsOSProxy.kt +++ b/src/main/kotlin/controllers/windows/WindowsOSProxy.kt @@ -115,7 +115,6 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { } enum class DwmWindowAttribute { - DWMWA_NCRENDERING_ENABLED, DWMWA_NCRENDERING_POLICY, DWMWA_TRANSITIONS_FORCEDISABLED, @@ -320,18 +319,15 @@ interface WindowsOSProxy : MousePointerObserver, OSProxy { val hWnd = user32.GetForegroundWindow() val rect = WinRect() - val success = user32.DwmGetWindowAttribute( - hWnd, - 8, - rect.pointer, - rect.size() - ) - return if (true){ - rect.read() - Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top) - }else{ - null - } +// val attr = user32.DwmGetWindowAttribute( +// hWnd, +// 8, +// rect.pointer, +// rect.size() +// ) + + rect.read() + return Rectangle(rect.top, rect.left, (rect.right - rect.left), rect.bottom - rect.top) } fun barelyFunctionalWindowQuery(): Rectangle? { diff --git a/src/main/kotlin/util/HelperFunctions.kt b/src/main/kotlin/util/HelperFunctions.kt index 65e42b1..44846c9 100644 --- a/src/main/kotlin/util/HelperFunctions.kt +++ b/src/main/kotlin/util/HelperFunctions.kt @@ -42,21 +42,38 @@ object HelperFunctions { * @param upperBound The upper bound to sample from * @return A random long value following an approximate normal distribution */ - fun getRandomLongFromNormalDistribution(upperBound: Long): Long{ + fun getGaussianLong(upperBound: Long): Long { // anything lower to 2 will round down to zero, so return zero. Additionally, this guarantees a positive upper //bound in one step - if(upperBound <= 2L){ + if (upperBound <= 2L) { return 0L } // To create more natural distribution Take two random samples from 0 to upperBound/2 and add them. This // approximates a normal distribution better than single random sample. - val subBound = upperBound/2L + val subBound = upperBound / 2L val result1 = Random.nextLong(subBound) val result2 = Random.nextLong(subBound) return result1 + result2 } + /** + * Generates a random long following a Gaussian distribution within the given upper bound. + * + * Models the distribution observed in getRandomLongFromNormalDistribution + * + * @param upperBound The upper bound of the distribution + * @return A random long value + */ + fun getNextGaussian(upperBound: Long): Long { + return java.util.Random() + .nextGaussian( + upperBound.toDouble() / 2.0, + upperBound.toDouble() / 5.0 + ).toLong() + .coerceIn(0..upperBound) + } + /** * Prints a progress report to console showing current step, total steps, elapsed time, and estimated remaining time. diff --git a/src/test/kotlin/util/HelperFunctionsTest.kt b/src/test/kotlin/util/HelperFunctionsTest.kt index 3b9a7aa..905667a 100644 --- a/src/test/kotlin/util/HelperFunctionsTest.kt +++ b/src/test/kotlin/util/HelperFunctionsTest.kt @@ -1,7 +1,9 @@ package util import kotlin.math.pow -import kotlin.test.* +import kotlin.system.measureNanoTime +import kotlin.system.measureTimeMillis +import kotlin.test.Test class HelperFunctionsTest { @@ -14,7 +16,7 @@ class HelperFunctionsTest { val numSamples = 10000 val upperBound = 1000L val samples = (1..numSamples).map { - HelperFunctions.getRandomLongFromNormalDistribution(upperBound) + HelperFunctions.getGaussianLong(upperBound) } // Calculate mean and standard deviation @@ -26,4 +28,117 @@ class HelperFunctionsTest { assert(stdDev > upperBound * 0.2 && stdDev < upperBound * 0.3) } + + @Test + fun `test benchmark getNextLongJanky`() { + val iterations = 1000000 + val upperBound = 1000L + + println("Starting benchmark N=$iterations, Range[0,$upperBound] using getRandomLongFromNormalDistribution...") + + repeat(10) { + val samples = ArrayList(iterations) + + val time = doBenchmark(iterations, upperBound) { + val v = HelperFunctions.getGaussianLong(it) + samples.add(v) + v + } + // Calculate mean and standard deviation + val mean = samples.average() + val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5) + + println("[Speed] Generated $iterations random longs in $time nanos [${(time.toDouble() / iterations.toDouble())}ns per operation]") + println("[Precision] Mean: $mean\tstDev: $stdDev\n") + } + } + + @Test + fun `test benchmark getNextLongGaussian`() { + + val iterations = 1000000 + val upperBound = 1000L + + println("Starting benchmark N=$iterations, Range[0,$upperBound] using getNextGaussian...") + + repeat(10) { + val samples = ArrayList(iterations) + + val time = doBenchmark(iterations, upperBound) { + val v = HelperFunctions.getNextGaussian(it) + samples.add(v) + v + } + // Calculate mean and standard deviation + val mean = samples.average() + val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5) + + println("[Speed] Generated $iterations random longs in $time nanos [${(time.toDouble() / iterations.toDouble())}ns per operation]") + println("[Precision] Mean: $mean\tstDev: $stdDev\n") + } + } + + fun eat(d: Number) { + + } + + fun doBenchmark(iterations: Int, upperBound: Long, callback: (Long) -> Long): Long { + var total = 0L + return measureNanoTime { + repeat(iterations) { + total += callback(upperBound) + } + } + } + + + @Test + fun `test Direct Comparison of performance`() { + val heats = 1000 + val iterations = 1000000 + val upperBound = 1000L + + val addTimes = ArrayList(heats) + val gaussianTimes = ArrayList(heats) + + //pre-warm the JIT and caches a bit + println("Warming up for a few iterations") + val consumer = ArrayList(iterations) + repeat(iterations){ + consumer.add(HelperFunctions.getGaussianLong(upperBound)) + consumer.add(HelperFunctions.getNextGaussian(upperBound)) + } + + println("Benchmarking methods [$heats heats of $iterations iterations]{Range[0,$upperBound]}...") + repeat(heats) { + var total = 0L + val time1 = measureNanoTime { + repeat(iterations) { + total += HelperFunctions.getGaussianLong(upperBound) + } + } + + addTimes.add(time1/iterations) + + total = 0L + val time2 = measureNanoTime { + repeat(iterations) { + total += HelperFunctions.getNextGaussian(upperBound) + } + } + + gaussianTimes.add(time2/iterations) + } + + printStatisticsWithPrefix(addTimes, "Add Randoms method:\t", "ns") + printStatisticsWithPrefix(gaussianTimes, "NextGaussian method:", "ns") + } + + fun printStatisticsWithPrefix(samples: ArrayList, prefix: String, unit: String = "") { + // Calculate mean and standard deviation + val mean = samples.average() + val stdDev = samples.map { (it - mean).pow(2) }.average().pow(0.5) + println("$prefix\tMean: $mean$unit\tstDev: $stdDev$unit") + } + } \ No newline at end of file