using System.Collections.Concurrent; namespace Sandbox; /// /// Strings are commonly converted to tokens in engine, to save space and speed up things like comparisons. /// We wrap this functionality up in the StringToken struct, because we can apply a bunch of compile time /// optimizations to speed up the conversion. /// public struct StringToken { static ConcurrentDictionary Cache = new( StringComparer.OrdinalIgnoreCase ); static ConcurrentDictionary CacheReverse = new(); public uint Value; public StringToken( string value ) { if ( value is null || value.Length == 0 ) return; Value = Cache.GetOrAdd( value, Calculate ); } public StringToken( uint token ) { this.Value = token; } static public implicit operator StringToken( string value ) => new StringToken( value ); static uint Calculate( string value ) { if ( value is null ) return 0; if ( value.Length == 0 ) return 0; var token = value.MurmurHash2( true ); CacheReverse[token] = value; return token; } /// /// called by interop /// internal static uint FindOrCreate( string str ) { return new StringToken( str ).Value; } internal static string GetValue( uint token ) { if ( CacheReverse.TryGetValue( token, out string val ) ) return val; return null; } /// /// This is used by codegen. String literals are replaced by this function call, which /// avoids having to create or lookup the string token. /// public static StringToken Literal( string value, uint token ) { return new StringToken( token ); } internal static bool TryLookup( uint token, out string outVal ) { if ( CacheReverse.TryGetValue( token, out outVal ) ) return true; // If we don't find the StringToken, assume that it was passed through native // (not through FindOrCreate), and try to grab it from the native database //var str = NativeEngine.EngineGlue.GetStringTokenValue( token ); //if ( !string.IsNullOrEmpty( str ) ) //{ // Add to to the cache so next time we don't have to check again // Cache.TryAdd( str, token ); // CacheReverse.TryAdd( token, str ); // outVal = str; // return true; //} return false; } /// /// A bunch of values we want to exist in the reverse lookup. /// I don't know if this is still strictly needed, but we used to need these to deserialize entities properly. /// static string[] defaults = new[] { "angles", "origin", "model", "owner", "velocity", "spawnflags", "avelocity", "ownername", "disableshadows", "disablereceiveshadows", "nodamageforces", "message", "gametitle", "targetname", "globalname", "rendercolor", "lightgroup", "rendertocubemaps", "lightmapstatic", "rendermode", "startdisabled", "skin", "bodygroups", "scale" }; static StringToken() { foreach ( var str in defaults ) { FindOrCreate( str ); } } /// /// To allow redirecting in the case where a class has both a string and StringToken version of a method. /// We should be able to remove this when we're compiling on demand instead of keeping the string versions around for compatibility. /// public class ConvertAttribute : System.Attribute { } }