From 0472091d7ce6ba6d82b96f1a6a0896bd9e40c3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 11 Mar 2025 19:45:57 +0100 Subject: [PATCH] (Probably) better `hashGeneric` (#1187) * Make hash function more complicated * Change seed value for array and vector hashes * Fix formatting issues * Fix formatting issues * Update src/server/terrain/biomes.zig --- src/server/terrain/biomes.zig | 45 ++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 88cece59..3f19d04d 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -142,16 +142,22 @@ const Stripe = struct { // MARK: Stripe fn hashGeneric(input: anytype) u64 { const T = @TypeOf(input); return switch(@typeInfo(T)) { - .bool => @intFromBool(input), - .@"enum" => @intFromEnum(input), - .int, .float => @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(input)), + .bool => hashCombine(hashInt(@intFromBool(input)), 0xbf58476d1ce4e5b9), + .@"enum" => hashCombine(hashInt(@as(u64, @intFromEnum(input))), 0x94d049bb133111eb), + .int, .float => blk: { + const value = @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(input)); + break :blk hashInt(@as(u64, value)); + }, .@"struct" => blk: { if(@hasDecl(T, "getHash")) { break :blk input.getHash(); } - var result: u64 = 0; + var result: u64 = hashGeneric(@typeName(T)); inline for(@typeInfo(T).@"struct".fields) |field| { - result ^= hashGeneric(@field(input, field.name))*%hashGeneric(@as([]const u8, field.name)); + const keyHash = hashGeneric(@as([]const u8, field.name)); + const valueHash = hashGeneric(@field(input, field.name)); + const keyValueHash = hashCombine(keyHash, valueHash); + result = hashCombine(result, keyValueHash); } break :blk result; }, @@ -164,25 +170,28 @@ fn hashGeneric(input: anytype) u64 { break :blk hashGeneric(input.*); }, .slice => blk: { - var result: u64 = 0; + var result: u64 = hashInt(input.len); for(input) |val| { - result = result*%33 +% hashGeneric(val); + const valueHash = hashGeneric(val); + result = hashCombine(result, valueHash); } break :blk result; }, else => @compileError("Unsupported type " ++ @typeName(T)), }, .array => blk: { - var result: u64 = 0; + var result: u64 = 0xbf58476d1ce4e5b9; for(input) |val| { - result = result*%33 +% hashGeneric(val); + const valueHash = hashGeneric(val); + result = hashCombine(result, valueHash); } break :blk result; }, .vector => blk: { - var result: u64 = 0; + var result: u64 = 0x94d049bb133111eb; inline for(0..@typeInfo(T).vector.len) |i| { - result = result*%33 +% hashGeneric(input[i]); + const valueHash = hashGeneric(input[i]); + result = hashCombine(result, valueHash); } break :blk result; }, @@ -190,6 +199,20 @@ fn hashGeneric(input: anytype) u64 { }; } +// https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes +fn hashCombine(left: u64, right: u64) u64 { + return left ^ (right +% 0x517cc1b727220a95 +% (left << 6) +% (left >> 2)); +} + +// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key +fn hashInt(input: u64) u64 { + var x = input; + x = (x ^ (x >> 30))*%0xbf58476d1ce4e5b9; + x = (x ^ (x >> 27))*%0x94d049bb133111eb; + x = x ^ (x >> 31); + return x; +} + pub const Interpolation = enum(u8) { none, linear,