diff --git a/lib/web_ui/lib/src/ui/hash_codes.dart b/lib/web_ui/lib/src/ui/hash_codes.dart index a5fa6a51d601b..5bfdf7b44c59a 100644 --- a/lib/web_ui/lib/src/ui/hash_codes.dart +++ b/lib/web_ui/lib/src/ui/hash_codes.dart @@ -4,102 +4,90 @@ part of ui; -class _HashEnd { - const _HashEnd(); -} - +class _HashEnd { const _HashEnd(); } const _HashEnd _hashEnd = _HashEnd(); -/// Combine up to twenty values' hashCodes into one value. +/// Jenkins hash function, optimized for small integers. +// +// Borrowed from the dart sdk: sdk/lib/math/jenkins_smi_hash.dart. +class _Jenkins { + static int combine(int hash, Object o) { + assert(o is! Iterable); + hash = 0x1fffffff & (hash + o.hashCode); + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + hash = hash ^ (hash >> 6); + return hash; + } + + static int finish(int hash) { + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = hash ^ (hash >> 11); + hash = 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + return hash; + } +} + +/// Combine up to twenty objects' hash codes into one value. /// -/// If you only need to handle one value's hashCode, then just refer to its -/// [hashCode] getter directly. +/// If you only need to handle one object's hash code, then just refer to its +/// [Object.hashCode] getter directly. /// -/// If you need to combine an arbitrary number of values from a List or other -/// Iterable, use [hashList]. The output of hashList can be used as one of the -/// arguments to this function. +/// If you need to combine an arbitrary number of objects from a [List] or other +/// [Iterable], use [hashList]. The output of [hashList] can be used as one of +/// the arguments to this function. /// /// For example: /// -/// int hashCode => hashValues(foo, bar, hashList(quux), baz); -int hashValues(Object arg01, Object arg02, - [Object arg03 = _hashEnd, - Object arg04 = _hashEnd, - Object arg05 = _hashEnd, - Object arg06 = _hashEnd, - Object arg07 = _hashEnd, - Object arg08 = _hashEnd, - Object arg09 = _hashEnd, - Object arg10 = _hashEnd, - Object arg11 = _hashEnd, - Object arg12 = _hashEnd, - Object arg13 = _hashEnd, - Object arg14 = _hashEnd, - Object arg15 = _hashEnd, - Object arg16 = _hashEnd, - Object arg17 = _hashEnd, - Object arg18 = _hashEnd, - Object arg19 = _hashEnd, - Object arg20 = _hashEnd]) { - int result = 373; - assert(arg01 is! Iterable); - result = 37 * result + arg01.hashCode; - assert(arg02 is! Iterable); - result = 37 * result + arg02.hashCode; +/// ```dart +/// int hashCode => hashValues(foo, bar, hashList(quux), baz); +/// ``` +int hashValues( + Object arg01, Object arg02, [ Object arg03 = _hashEnd, + Object arg04 = _hashEnd, Object arg05 = _hashEnd, Object arg06 = _hashEnd, + Object arg07 = _hashEnd, Object arg08 = _hashEnd, Object arg09 = _hashEnd, + Object arg10 = _hashEnd, Object arg11 = _hashEnd, Object arg12 = _hashEnd, + Object arg13 = _hashEnd, Object arg14 = _hashEnd, Object arg15 = _hashEnd, + Object arg16 = _hashEnd, Object arg17 = _hashEnd, Object arg18 = _hashEnd, + Object arg19 = _hashEnd, Object arg20 = _hashEnd ]) { + int result = 0; + result = _Jenkins.combine(result, arg01); + result = _Jenkins.combine(result, arg02); if (arg03 != _hashEnd) { - assert(arg03 is! Iterable); - result = 37 * result + arg03.hashCode; + result = _Jenkins.combine(result, arg03); if (arg04 != _hashEnd) { - assert(arg04 is! Iterable); - result = 37 * result + arg04.hashCode; + result = _Jenkins.combine(result, arg04); if (arg05 != _hashEnd) { - assert(arg05 is! Iterable); - result = 37 * result + arg05.hashCode; + result = _Jenkins.combine(result, arg05); if (arg06 != _hashEnd) { - assert(arg06 is! Iterable); - result = 37 * result + arg06.hashCode; + result = _Jenkins.combine(result, arg06); if (arg07 != _hashEnd) { - assert(arg07 is! Iterable); - result = 37 * result + arg07.hashCode; + result = _Jenkins.combine(result, arg07); if (arg08 != _hashEnd) { - assert(arg08 is! Iterable); - result = 37 * result + arg08.hashCode; + result = _Jenkins.combine(result, arg08); if (arg09 != _hashEnd) { - assert(arg09 is! Iterable); - result = 37 * result + arg09.hashCode; + result = _Jenkins.combine(result, arg09); if (arg10 != _hashEnd) { - assert(arg10 is! Iterable); - result = 37 * result + arg10.hashCode; + result = _Jenkins.combine(result, arg10); if (arg11 != _hashEnd) { - assert(arg11 is! Iterable); - result = 37 * result + arg11.hashCode; + result = _Jenkins.combine(result, arg11); if (arg12 != _hashEnd) { - assert(arg12 is! Iterable); - result = 37 * result + arg12.hashCode; + result = _Jenkins.combine(result, arg12); if (arg13 != _hashEnd) { - assert(arg13 is! Iterable); - result = 37 * result + arg13.hashCode; + result = _Jenkins.combine(result, arg13); if (arg14 != _hashEnd) { - assert(arg14 is! Iterable); - result = 37 * result + arg14.hashCode; + result = _Jenkins.combine(result, arg14); if (arg15 != _hashEnd) { - assert(arg15 is! Iterable); - result = 37 * result + arg15.hashCode; + result = _Jenkins.combine(result, arg15); if (arg16 != _hashEnd) { - assert(arg16 is! Iterable); - result = 37 * result + arg16.hashCode; + result = _Jenkins.combine(result, arg16); if (arg17 != _hashEnd) { - assert(arg17 is! Iterable); - result = 37 * result + arg17.hashCode; + result = _Jenkins.combine(result, arg17); if (arg18 != _hashEnd) { - assert(arg18 is! Iterable); - result = 37 * result + arg18.hashCode; + result = _Jenkins.combine(result, arg18); if (arg19 != _hashEnd) { - assert(arg19 is! Iterable); - result = 37 * result + arg19.hashCode; + result = _Jenkins.combine(result, arg19); if (arg20 != _hashEnd) { - assert(arg20 is! Iterable); - result = 37 * result + arg20.hashCode; + result = _Jenkins.combine(result, arg20); // I can see my house from here! } } @@ -119,19 +107,17 @@ int hashValues(Object arg01, Object arg02, } } } - return result; + return _Jenkins.finish(result); } -/// Combine the hashCodes of an arbitrary number of values from an Iterable into -/// one value. This function will return the same value if given "null" as if -/// given an empty list. -int hashList(Iterable args) { - int result = 373; - if (args != null) { - for (Object arg in args) { - assert(arg is! Iterable); - result = 37 * result + arg.hashCode; - } +/// Combine the [Object.hashCode] values of an arbitrary number of objects from +/// an [Iterable] into one value. This function will return the same value if +/// given null as if given an empty list. +int hashList(Iterable arguments) { + int result = 0; + if (arguments != null) { + for (Object argument in arguments) + result = _Jenkins.combine(result, argument); } - return result; + return _Jenkins.finish(result); } diff --git a/lib/web_ui/test/hash_codes_test.dart b/lib/web_ui/test/hash_codes_test.dart new file mode 100644 index 0000000000000..a0950bfaf68c1 --- /dev/null +++ b/lib/web_ui/test/hash_codes_test.dart @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:test/test.dart'; +import 'package:ui/ui.dart'; + +// The biggest integer value that can be represented in JavaScript is 1 << 53. +// However, the 1 << 53 expression cannot be used in JavaScript because that +// would apply the bitwise shift to a "number" (i.e. float64), which is +// meaningless. Instead, a decimal literal is used here. +const int _kBiggestExactJavaScriptInt = 9007199254740992; + +void main() { + test('hashValues can hash lots of huge values effectively', () { + expect( + hashValues( + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + ), + 496984395, + ); + // Hash a slightly smaller number to verify that the hash code is different. + expect( + hashValues( + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt - 1, + ), + 455584273, + ); + }); + + test('hashList can hash lots of huge values effectively', () { + expect( + hashList([ + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + ]), + 496984395, + ); + // Hash a slightly smaller number to verify that the hash code is different. + expect( + hashList([ + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt, + _kBiggestExactJavaScriptInt - 1, + ]), + 455584273, + ); + }); +}