Editor/Services/ArchHash.cs

Utility class that implements a deterministic 64-bit FNV-1a style rolling hash used to fold different value types into a single cache key. Provides Fold overloads for ulong, int, bool, float, Vector2, Vector3 and string, and a convenience Of(string) starting from a fixed seed.

Native Interop
using System;
using Sandbox;

namespace Sunless.Architecture;

// The one fold every cache key in the tool is built from, so no two keys can disagree about what "the same"
// means. FNV-1a over 64 bits, and ORDER MATTERS in it - which is exactly what an ordered operation stack needs
// from a rolled-up hash: two layers swapped in the stack are a different build, not the same one.
public static class ArchHash
{
	public const ulong Seed = 14695981039346656037ul;

	const ulong Prime = 1099511628211ul;

	public static ulong Fold( ulong hash, ulong value )
	{
		for ( var shift = 0; shift < 64; shift += 8 )
		{
			hash = (hash ^ ((value >> shift) & 0xFF)) * Prime;
		}

		return hash;
	}

	public static ulong Fold( ulong hash, int value ) => Fold( hash, (ulong)(uint)value );

	public static ulong Fold( ulong hash, bool value ) => Fold( hash, value ? 1 : 0 );

	// Negative zero carries different bits to zero, and a snap can land on either - collapsing it keeps two
	// identical builds on one key instead of missing the cache over a sign nothing can see.
	public static ulong Fold( ulong hash, float value )
	{
		return Fold( hash, (ulong)(uint)BitConverter.SingleToInt32Bits( value == 0f ? 0f : value ) );
	}

	public static ulong Fold( ulong hash, Vector2 value ) => Fold( Fold( hash, value.x ), value.y );

	public static ulong Fold( ulong hash, Vector3 value ) => Fold( Fold( Fold( hash, value.x ), value.y ), value.z );

	public static ulong Fold( ulong hash, string value )
	{
		if ( value is null )
		{
			return Fold( hash, -1 );
		}

		hash = Fold( hash, value.Length );

		foreach ( var character in value )
		{
			hash = Fold( hash, character );
		}

		return hash;
	}

	public static ulong Of( string value ) => Fold( Seed, value );
}