using NativeEngine; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; namespace Sandbox { public partial class Model { Dictionary DataCache = new(); readonly JsonSerializerOptions jsonOptions = new() { ReadCommentHandling = JsonCommentHandling.Skip, PropertyNameCaseInsensitive = true, IncludeFields = true, Converters = { new JsonStringEnumConverter() } }; internal string GetJson( string keyname ) { if ( string.IsNullOrEmpty( Name ) ) Log.Warning( $"Warning: Trying to access Model Key Value '{keyname}' on an empty model!" ); var key = native.FindModelSubKey( keyname ); if ( key.IsNull ) return null; return EngineGlue.KeyValues3ToJson( key ); } internal string DeduceKeyName( Type type ) { var outputType = type; var dataType = type; if ( dataType.IsArray ) { dataType = dataType.GetElementType(); } // Try to warn if the data types are mismatched var gdAttr = dataType.GetCustomAttribute(); if ( gdAttr == null ) throw new ArgumentException( $"{type.Name} does not have ModelDoc.GameDataAttribute" ); if ( !gdAttr.AllowMultiple && outputType.IsArray ) throw new ArgumentException( $"{type.Name} is not a list, but an array generic parameter was given" ); if ( gdAttr.AllowMultiple && !outputType.IsArray ) throw new ArgumentException( $"{type.Name} is a list, but non array generic parameter was given" ); var keyname = gdAttr.Name; if ( gdAttr.AllowMultiple ) keyname = gdAttr.ListName ?? $"{keyname}_list"; return keyname; } /// /// Tries to extract data from model based on the given type's ModelDoc.GameDataAttribute. /// /// The extracted data, or default on failure. /// true if data was extracted successfully, false otherwise. public bool TryGetData( out T data ) { bool ret = TryGetData( typeof( T ), out object dat ); data = (T)dat; return ret; } /// /// Tries to extract data from model based on the given type's ModelDoc.GameDataAttribute. /// /// The extracted data, or default on failure. /// The class with ModelDoc.GameDataAttribute. /// true if data was extracted successfully, false otherwise. public bool TryGetData( Type t, out object data ) { string keyname = DeduceKeyName( t ); string json; // Try to get data from cache var cacheKey = $"{keyname}~{t.Name}"; if ( DataCache != null && DataCache.TryGetValue( cacheKey, out object cacheValue ) ) { json = (string)cacheValue; } else { json = GetJson( keyname ); if ( string.IsNullOrWhiteSpace( json ) ) { data = default; return false; } // Special case of nodes that have no properties if ( json == "null" ) json = "{}"; // Add to cache if ( DataCache == null ) DataCache = new Dictionary(); DataCache.Add( cacheKey, json ); } try { var obj = JsonSerializer.Deserialize( json, t, jsonOptions ); if ( obj == null ) { data = default; return false; } data = obj; return true; } catch ( Exception e ) { Log.Warning( e, $"Failed to deserialize '{keyname}' to {t.Name}" ); } data = default; return false; } /// /// Tests if this model has generic data based on given type's ModelDoc.GameDataAttribute. /// This will be faster than testing this via GetData]]>() /// public bool HasData() { var keyname = DeduceKeyName( typeof( T ) ); return native.FindModelSubKey( keyname ).IsValid; } /// /// Extracts data from model based on the given type's ModelDoc.GameDataAttribute. /// public T GetData() { if ( TryGetData( out T data ) ) { return data; } return default; } #region BreakCommands /// /// Internal function used to get a list of break commands the model has. /// public Dictionary GetBreakCommands() { var cacheKey = $"break_command_list~{Name}"; if ( DataCache != null && DataCache.TryGetValue( cacheKey, out object cacheValue ) ) { return (Dictionary)cacheValue; } Dictionary output = new(); var jsonString = GetJson( "break_command_list" ); if ( jsonString == null ) return output; using ( JsonDocument document = JsonDocument.Parse( jsonString ) ) { JsonElement root = document.RootElement; if ( root.ValueKind != JsonValueKind.Array ) return output; foreach ( JsonElement commandData in root.EnumerateArray() ) { if ( commandData.TryGetProperty( "break_command", out JsonElement command ) ) { var cmd = command.GetString(); List arr = output.ContainsKey( cmd ) ? output[cmd].Cast().ToList() : new(); arr.Add( commandData.GetRawText() ); output[cmd] = arr.ToArray(); } } } if ( DataCache == null ) DataCache = new Dictionary(); DataCache.Add( cacheKey, output ); return output; } #endregion } }