Little things
This commit is contained in:
parent
81ba16a0d1
commit
4d9cda5f48
189
data/JsonData.cs
189
data/JsonData.cs
@ -1,16 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
static public class json
|
||||
{
|
||||
|
||||
|
||||
static public T load<T>( string filename ) where T : lib.JsonData
|
||||
{
|
||||
return (T)lib.JsonData.load<T>( filename );
|
||||
@ -18,31 +14,47 @@ static public class json
|
||||
|
||||
static public lib.JsonData load( string filename )
|
||||
{
|
||||
return lib.ConfigBase.load( filename, null );
|
||||
return lib.JsonData.load( filename );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace lib
|
||||
{
|
||||
|
||||
// Merged the Interface and Class logic to remove ambiguity
|
||||
public interface JsonData
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
// Centralized JSON Options for consistency across Load/Save
|
||||
private static readonly JsonSerializerOptions _options = new()
|
||||
{
|
||||
WriteIndented = true, // Pretty print
|
||||
PropertyNameCaseInsensitive = true, // Forgiving load
|
||||
IncludeFields = true, // Serialize public fields (game dev standard)
|
||||
ReferenceHandler = ReferenceHandler.Preserve, // Handles circular refs and polymorphism $id metadata
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // Cleaner files
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveLoad
|
||||
|
||||
// Entry point for Generic Load
|
||||
static public T res_load<T>( string filename ) where T : JsonData
|
||||
where T : class
|
||||
{
|
||||
return JsonData.load<T>( filename );
|
||||
return (T)actual_load( filename, typeof( T ) );
|
||||
}
|
||||
|
||||
// Entry point for untyped Load
|
||||
static public JsonData load( string filename )
|
||||
{
|
||||
return actual_load( filename, null );
|
||||
}
|
||||
|
||||
// Wrapper for generic load
|
||||
static public T load<T>( string filename ) where T : JsonData
|
||||
{
|
||||
return (T)actual_load( filename, typeof( T ) );
|
||||
@ -50,147 +62,138 @@ namespace lib
|
||||
|
||||
static public JsonData actual_load( string filename, Type t )
|
||||
{
|
||||
JsonData cfg = null;
|
||||
JsonData json = null;
|
||||
|
||||
try
|
||||
{
|
||||
if( !File.Exists( filename ) )
|
||||
// 1. Path Sanitization
|
||||
if( !File.Exists( filename ) && filename.StartsWith( "res://" ) )
|
||||
{
|
||||
if( filename.StartsWith( "res://" ) )
|
||||
filename.Replace( "res://", "" );
|
||||
filename = filename.Replace( "res://", "" );
|
||||
}
|
||||
|
||||
|
||||
// 2. Load or Create
|
||||
if( File.Exists( filename ) )
|
||||
{
|
||||
FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read );
|
||||
string jsonContent = File.ReadAllText( filename );
|
||||
|
||||
XmlFormatter2 formatter = new XmlFormatter2();
|
||||
// If T is null, we can't deserialize effectively without Type info.
|
||||
// Defaulting to JsonData or the passed type.
|
||||
Type targetType = t ?? typeof( JsonData );
|
||||
|
||||
cfg = (JsonData)( t != null ? formatter.DeserializeKnownType( fs, t ) : formatter.Deserialize( fs ) );
|
||||
json = (JsonData)JsonSerializer.Deserialize( jsonContent, targetType, _options );
|
||||
|
||||
cfg.SetFilename( filename );
|
||||
if( json != null )
|
||||
{
|
||||
json.Filename = filename;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cfg = CreateTemplate( filename, t );
|
||||
json = CreateTemplate( filename, t );
|
||||
}
|
||||
}
|
||||
catch( IOException )
|
||||
catch( Exception ex ) when( ex is IOException || ex is JsonException )
|
||||
{
|
||||
cfg = CreateTemplate( filename, t );
|
||||
log.error( $"Failed to load {filename}: {ex.Message}" );
|
||||
json = CreateTemplate( filename, t );
|
||||
}
|
||||
|
||||
return cfg;
|
||||
return json;
|
||||
}
|
||||
|
||||
private static JsonData CreateTemplate( string filename, Type t )
|
||||
{
|
||||
log.debug( $"JsonData file {filename} not found, creating template." );
|
||||
|
||||
Type[] types = new Type[0];
|
||||
object[] parms = new object[0];
|
||||
JsonData json = null;
|
||||
|
||||
//types[ 0 ] = typeof( string );
|
||||
//parms[ 0 ] = filename;
|
||||
JsonData cfg = null;
|
||||
|
||||
ConstructorInfo cons = t?.GetConstructor( types );
|
||||
// Handle potential null type if called generically without constraints
|
||||
if( t == null )
|
||||
{
|
||||
log.error( "Cannot create template for unknown type." );
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cfg = (JsonData)cons?.Invoke( parms );
|
||||
json = refl.CreateObject( t ) as JsonData;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
log.error( $"Exception while creating config {t.ToString()}, Msg {e.Message}" );
|
||||
log.error( $"Exception while creating config {t}, Msg {e.Message}" );
|
||||
}
|
||||
|
||||
//cfg.SetFilename( filename );
|
||||
|
||||
if( ConfigCfg.s_cfg.writeOutTemplateFiles )
|
||||
// Write out the template if configured to do so
|
||||
if( json != null && ConfigCfg.s_cfg.writeOutTemplateFiles )
|
||||
{
|
||||
var templateFile = $"templates/{filename}";
|
||||
|
||||
var dirName = Path.GetDirectoryName( templateFile );
|
||||
|
||||
Util.checkAndAddDirectory( dirName );
|
||||
|
||||
log.info( $"Writing out template config of type {t?.Name} in {templateFile}" );
|
||||
|
||||
saveDebug( cfg, templateFile );
|
||||
// Force set filename for the template save, then restore?
|
||||
// Or just pass path to saveDebug.
|
||||
json.Filename = templateFile;
|
||||
json.save();
|
||||
}
|
||||
|
||||
return cfg;
|
||||
return json;
|
||||
}
|
||||
|
||||
static public void saveDebug( JsonData cfg )
|
||||
static public void actual_save( JsonData json, string filename )
|
||||
{
|
||||
JsonData.actual_save( cfg, cfg.Filename );
|
||||
}
|
||||
try
|
||||
{
|
||||
// Ensure directory exists
|
||||
string dir = Path.GetDirectoryName( filename );
|
||||
if( !string.IsNullOrEmpty( dir ) && !Directory.Exists( dir ) )
|
||||
{
|
||||
Directory.CreateDirectory( dir );
|
||||
}
|
||||
|
||||
static public void actual_save( JsonData cfg, String filename )
|
||||
{
|
||||
FileStream fs = new( filename, FileMode.Create, FileAccess.Write );
|
||||
fs.Close();
|
||||
// Serialize to string first (safer than open stream + fail)
|
||||
string jsonString = JsonSerializer.Serialize( json, json.GetType(), _options );
|
||||
File.WriteAllText( filename, jsonString );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"Type { json.GetType().Name } {filename}: {ex.Message}" );
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Instance Logic
|
||||
|
||||
public String Filename { get; }
|
||||
}
|
||||
//private string _filename = "{unknown}";
|
||||
public string Filename { get; set; }
|
||||
|
||||
/* UNUSED
|
||||
public record class ConfigRec : ConfigBase
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
public class JsonData : ConfigBase
|
||||
{
|
||||
|
||||
//private int _test = 0;
|
||||
|
||||
private static ser.XmlCfg s_templateCfg = new()
|
||||
static public void startup()
|
||||
{
|
||||
Structure = ser.Datastructure.Tree,
|
||||
POD = ser.POD.Elements,
|
||||
};
|
||||
|
||||
|
||||
internal static ConfigCfg s_cfg = new();
|
||||
|
||||
public static ConfigCfg Cfg => s_cfg;
|
||||
|
||||
static public void startup( string filename )
|
||||
{
|
||||
res.Mgr.Register( ConfigBase.load );
|
||||
// Assuming res.Mgr handles the delegates correctly
|
||||
//res.Mgr.Register( JsonData.load );
|
||||
res.Mgr.RegisterSub( typeof( ConfigBase ) );
|
||||
|
||||
s_cfg = ConfigBase.load<ConfigCfg>( filename );
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class JsonDataRes : JsonData, res.Resource
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
}
|
||||
|
||||
public JsonData()
|
||||
static public class JsonDataEx
|
||||
{
|
||||
|
||||
static public void save( this JsonData json )
|
||||
{
|
||||
// Allow overriding path for templates, otherwise use object's filename
|
||||
JsonData.actual_save( json, json.Filename );
|
||||
}
|
||||
|
||||
public JsonData( string filename )
|
||||
{
|
||||
_filename = filename;
|
||||
}
|
||||
|
||||
private string _filename = "{unknown}";
|
||||
|
||||
|
||||
public String Filename { get { return _filename; } }
|
||||
|
||||
internal void SetFilename( String filename ) { _filename = filename; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public static class imm
|
||||
ref T obj,
|
||||
Func<T, T> fn,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string dbgExpression = "" )
|
||||
@ -37,7 +37,7 @@ public static class imm
|
||||
{
|
||||
// This will call the 'Timed' override if T is Timed,
|
||||
// or the 'Recorded' override if T is Recorded.
|
||||
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
|
||||
obj = obj.Process( fn, reason, dbgMethod, dbgPath, dbgLine, dbgExpression );
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
32
imm/io.cs
32
imm/io.cs
@ -31,7 +31,7 @@ public interface Obj
|
||||
/// </summary>
|
||||
Obj Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 );
|
||||
}
|
||||
@ -61,7 +61,7 @@ public interface Obj<T> : Obj where T : Obj<T>
|
||||
T Process(
|
||||
Func<T, T> fn,
|
||||
string reason = "Processed",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string expStr = "" );
|
||||
@ -73,7 +73,7 @@ public interface Obj<T> : Obj where T : Obj<T>
|
||||
/// </summary>
|
||||
new T Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 );
|
||||
}
|
||||
@ -190,7 +190,7 @@ public record class Versioned<T> : Obj<T> where T : Versioned<T>
|
||||
public virtual T Process(
|
||||
Func<T, T> fn,
|
||||
string reason = "Processed",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string expStr = "" )
|
||||
@ -215,18 +215,18 @@ public record class Versioned<T> : Obj<T> where T : Versioned<T>
|
||||
/// </summary>
|
||||
public virtual T Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgName, dbgPath, dbgLine );
|
||||
[CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgMethod, dbgPath, dbgLine );
|
||||
|
||||
/// <summary>
|
||||
/// Implements Obj.Record by calling the virtual T Record.
|
||||
/// </summary>
|
||||
Obj Obj.Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgName, dbgPath, dbgLine );
|
||||
[CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgMethod, dbgPath, dbgLine );
|
||||
|
||||
|
||||
}
|
||||
@ -250,7 +250,7 @@ public record class Recorded<T> : Versioned<T> where T : Recorded<T>
|
||||
public override T Process(
|
||||
Func<T, T> fn,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string expStr = "" )
|
||||
@ -265,7 +265,7 @@ public record class Recorded<T> : Versioned<T> where T : Recorded<T>
|
||||
{
|
||||
Version = current.Meta.Version + 1,
|
||||
Reason = reason,
|
||||
MemberName = dbgName,
|
||||
MemberName = dbgMethod,
|
||||
FilePath = dbgPath,
|
||||
LineNumber = dbgLine,
|
||||
Expression = expStr,
|
||||
@ -279,11 +279,11 @@ public record class Recorded<T> : Versioned<T> where T : Recorded<T>
|
||||
|
||||
public new T Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
{
|
||||
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
|
||||
return Process( t => t, reason, dbgMethod, dbgPath, dbgLine );
|
||||
}
|
||||
|
||||
}
|
||||
@ -351,11 +351,11 @@ public record class Timed<T> : Recorded<T> where T : Timed<T>
|
||||
|
||||
public new T Record(
|
||||
string reason = "Recorded",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
{
|
||||
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
|
||||
return Process( t => t, reason, dbgMethod, dbgPath, dbgLine );
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,13 +366,13 @@ public static class TimedExt
|
||||
ref T obj,
|
||||
Func<T, T> fn,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string dbgExpression = ""
|
||||
) where T : io.Timed<T>
|
||||
{
|
||||
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
|
||||
obj = obj.Process( fn, reason, dbgMethod, dbgPath, dbgLine, dbgExpression );
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,34 +625,56 @@ static public class log
|
||||
logBase( $"{header}{dbgExpObj}[{type.Name}] ->", level, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj );
|
||||
|
||||
// 6. Handle Complex Objects (Properties)
|
||||
if( maxDepth == 0 )
|
||||
if( maxDepth < 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<MemberInfo> members = new();
|
||||
refl.GetAllMembers( type, members );
|
||||
|
||||
// Get all readable properties
|
||||
var props = type.GetProperties( BindingFlags.Public | BindingFlags.Instance )
|
||||
.Where( p => p.CanRead && p.GetIndexParameters().Length == 0 );
|
||||
//var props = type.GetProperties( BindingFlags.Public | BindingFlags.Private | BindingFlags.Instance )
|
||||
// .Where( p => p.CanRead && p.GetIndexParameters().Length == 0 );
|
||||
|
||||
var props = members.Where( ( mi) => true );
|
||||
|
||||
var nextHeader = $"{header}{header}";
|
||||
|
||||
//bool firstProp = true;
|
||||
foreach( var pi in props )
|
||||
foreach( var mi in members )
|
||||
{
|
||||
var name = pi.Name;
|
||||
var piType = pi.PropertyType;
|
||||
var is_pi = mi is PropertyInfo pi;
|
||||
var is_fi = mi is FieldInfo fi;
|
||||
|
||||
var name = mi.Name;
|
||||
var piType = mi.MemberType;
|
||||
try
|
||||
{
|
||||
var value = pi.GetValue( obj );
|
||||
object? value = GetValue( obj, mi );
|
||||
props_r( value, nextHeader, --maxDepth, level, cat, prefix, dbgPath, dbgLine, dbgMethod, name );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
logBase( $"{nextHeader}{name}[{piType.Name}] ex {ex.Message}", level, dbgPath, dbgLine, dbgMethod, cat, name );
|
||||
logBase( $"{nextHeader}{name}[{mi.Name}] ex {ex.Message}", level, dbgPath, dbgLine, dbgMethod, cat, name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static object GetValue( object obj, MemberInfo mi )
|
||||
{
|
||||
if( mi is PropertyInfo pi )
|
||||
{
|
||||
return pi.GetValue( obj );
|
||||
}
|
||||
if( mi is FieldInfo fi )
|
||||
{
|
||||
return fi.GetValue( obj );
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
//This might seem a little odd, but the intent is that usually you wont need to set notExpectedValue.
|
||||
static public void expected<T>( T value, string falseString, string trueString = "", T? notExpectedValue = default( T ) )
|
||||
|
||||
104
reflect/refl.cs
104
reflect/refl.cs
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -18,6 +19,28 @@ using System.Threading.Tasks;
|
||||
static public class refl
|
||||
{
|
||||
|
||||
static public T Create<T>()
|
||||
{
|
||||
var t = typeof( T );
|
||||
return (T)CreateObject( t );
|
||||
}
|
||||
|
||||
static public object CreateObject( Type type )
|
||||
{
|
||||
object newObj = null;
|
||||
if( type.GetConstructor( Type.EmptyTypes ) != null )
|
||||
{
|
||||
newObj = Activator.CreateInstance( type );
|
||||
}
|
||||
else
|
||||
{
|
||||
newObj = FormatterServices.GetUninitializedObject( type );
|
||||
}
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
|
||||
public class PredEnumerator
|
||||
{
|
||||
public static PredEnumerator<T> Create<T>( IEnumerator<T> en, Predicate<T> pred )
|
||||
@ -142,6 +165,83 @@ static public class refl
|
||||
}
|
||||
|
||||
|
||||
public static void GetAllMembers( Type t, List<MemberInfo> list )
|
||||
{
|
||||
GetAllMembersRecursive( t, true, list );
|
||||
}
|
||||
|
||||
public static void GetAllMembersRecursive( Type t, bool recurse, List<MemberInfo> list )
|
||||
{
|
||||
var MemberArr = t.GetMembers(
|
||||
BindingFlags.DeclaredOnly |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance );
|
||||
|
||||
var en = PredEnumerator.Create<MemberInfo>( MemberArr.AsEnumerable<MemberInfo>(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null );
|
||||
|
||||
list.AddRange( new PredEnumerable<MemberInfo>( en ) );
|
||||
|
||||
if( recurse && t.BaseType != null && t.BaseType != typeof( object ) )
|
||||
{
|
||||
GetAllMembers( t.BaseType, list );
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetAllMembersUntil( Type t, Type tooFar, List<MemberInfo> list )
|
||||
{
|
||||
var MemberArr = t.GetMembers(
|
||||
BindingFlags.DeclaredOnly |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance );
|
||||
|
||||
var en = PredEnumerator.Create<MemberInfo>( MemberArr.AsEnumerable<MemberInfo>(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null );
|
||||
|
||||
list.AddRange( new PredEnumerable<MemberInfo>( en ) );
|
||||
|
||||
if( t.BaseType != null && t.BaseType != tooFar )
|
||||
{
|
||||
GetAllMembers( t.BaseType, list );
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableList<MemberInfo> GetAllMembers<T>()
|
||||
{
|
||||
return GetAllMembers( typeof( T ) );
|
||||
}
|
||||
|
||||
public static ImmutableList<MemberInfo> GetAllMembers( Type t )
|
||||
{
|
||||
{
|
||||
if( s_MemberCache.TryGetValue( t, out var first ) )
|
||||
return first;
|
||||
}
|
||||
|
||||
//LogGC.RegisterObjectId( t );
|
||||
|
||||
lock( t )
|
||||
{
|
||||
if( s_MemberCache.TryGetValue( t, out var second ) )
|
||||
return second;
|
||||
|
||||
var list = new List<MemberInfo>();
|
||||
|
||||
GetAllMembers( t, list );
|
||||
|
||||
var immList = list.ToImmutableList();
|
||||
|
||||
Interlocked.Exchange( ref s_MemberCache, s_MemberCache.Add( t, immList ) );
|
||||
|
||||
return immList;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static void GetAllFields( Type t, List<FieldInfo> list )
|
||||
{
|
||||
GetAllFieldsRecursive( t, true, list );
|
||||
@ -164,6 +264,7 @@ static public class refl
|
||||
GetAllFields( t.BaseType, list );
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetAllFieldsUntil( Type t, Type tooFar, List<FieldInfo> list )
|
||||
{
|
||||
var fieldArr = t.GetFields(
|
||||
@ -187,8 +288,6 @@ static public class refl
|
||||
return GetAllFields( typeof( T ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static ImmutableList<FieldInfo> GetAllFields( Type t )
|
||||
{
|
||||
{
|
||||
@ -253,6 +352,7 @@ static public class refl
|
||||
}
|
||||
|
||||
|
||||
static ImmutableDictionary<Type, ImmutableList<MemberInfo>> s_MemberCache = ImmutableDictionary<Type, ImmutableList<MemberInfo>>.Empty;
|
||||
static ImmutableDictionary<Type, ImmutableList<FieldInfo>> s_fieldCache = ImmutableDictionary<Type, ImmutableList<FieldInfo>>.Empty;
|
||||
static ImmutableDictionary<Type, ImmutableList<PropertyInfo>> s_propCache = ImmutableDictionary<Type, ImmutableList<PropertyInfo>>.Empty;
|
||||
|
||||
|
||||
@ -7,11 +7,17 @@ using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace res;
|
||||
|
||||
public interface Resource
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
}
|
||||
|
||||
// A delegate representing a function that can load a resource of type T.
|
||||
public delegate T Load<out T>( string filename );
|
||||
|
||||
@ -33,13 +39,13 @@ public abstract class Ref
|
||||
protected Ref(
|
||||
string filename,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
{
|
||||
Filename = filename;
|
||||
Reason = reason;
|
||||
DbgName = dbgName;
|
||||
DbgName = dbgMethod;
|
||||
DbgPath = dbgPath;
|
||||
DbgLine = dbgLine;
|
||||
|
||||
@ -53,7 +59,7 @@ public abstract class Ref
|
||||
/// <returns>The loaded resource object.</returns>
|
||||
public abstract object Lookup(
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 );
|
||||
|
||||
@ -66,6 +72,41 @@ public abstract class Ref
|
||||
/// Internal method to trigger the initial load (used by deferred loading, if implemented).
|
||||
/// </summary>
|
||||
internal virtual void InternalLoad() { }
|
||||
|
||||
static public res.Ref<T> Create<T>( T res, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, Resource, new()
|
||||
{
|
||||
if( res.Filename == null )
|
||||
{
|
||||
res.Filename = $"{{{typeof( T ).Name}}}.{Ref<T>.s_codeRes}";
|
||||
log.debug( $"Code Res for {typeof( T ).Name} named {res.Filename}" );
|
||||
}
|
||||
|
||||
return Ref<T>.CreateAsset( res, res.Filename );
|
||||
}
|
||||
|
||||
static public res.Ref<T> Create<T>( string filename, T res, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, new()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( filename ) )
|
||||
{
|
||||
filename = $"{{{typeof( T ).Name}}}.{Ref<T>.s_codeRes}";
|
||||
log.debug( $"Code Res for {typeof( T ).Name} named {filename}" );
|
||||
}
|
||||
|
||||
return Ref<T>.CreateAsset( res, filename );
|
||||
}
|
||||
|
||||
static public res.Ref<T> Create<T>( string filename, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, new()
|
||||
{
|
||||
if( string.IsNullOrWhiteSpace( filename ) )
|
||||
{
|
||||
filename = $"{{{typeof( T ).Name}}}.{Ref<T>.s_codeRes}";
|
||||
log.debug( $"Code Res for {typeof( T ).Name} named {filename}" );
|
||||
}
|
||||
|
||||
var res = refl.Create<T>();
|
||||
|
||||
return Ref<T>.CreateAsset( res, filename );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,6 +117,9 @@ public abstract class Ref
|
||||
[DebuggerDisplay( "Path = {Filename} / Res = {m_res}" )]
|
||||
public class Ref<T> : Ref where T : class, new()
|
||||
{
|
||||
static internal int s_codeRes = 1024;
|
||||
|
||||
[JsonIgnore]
|
||||
[NonSerialized]
|
||||
private T? m_res;
|
||||
|
||||
@ -88,17 +132,18 @@ public class Ref<T> : Ref where T : class, new()
|
||||
/// Gets the resource, loading it if necessary.
|
||||
/// </summary>
|
||||
//[Deprecated("Use Res property instead.")]
|
||||
[JsonIgnore]
|
||||
public T res => m_res ?? Lookup();
|
||||
|
||||
public Ref(
|
||||
string filename = "",
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
: base(
|
||||
!string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgName}_{Path.GetFileNameWithoutExtension( dbgPath )}}}",
|
||||
reason, dbgName, dbgPath, dbgLine )
|
||||
!string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgMethod}_{Path.GetFileNameWithoutExtension( dbgPath )}}}",
|
||||
reason, dbgMethod, dbgPath, dbgLine )
|
||||
{
|
||||
if( VerboseLogging )
|
||||
log.info( $"Ref<T> Created: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
|
||||
@ -110,7 +155,7 @@ public class Ref<T> : Ref where T : class, new()
|
||||
/// </summary>
|
||||
public override T Lookup(
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
{
|
||||
@ -119,7 +164,7 @@ public class Ref<T> : Ref where T : class, new()
|
||||
return m_res;
|
||||
|
||||
// Load using the Mgr.
|
||||
m_res = Mgr.Load<T>( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgName, dbgPath, dbgLine );
|
||||
m_res = Mgr.Load<T>( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgMethod, dbgPath, dbgLine );
|
||||
if( VerboseLogging )
|
||||
log.info( $"Ref.Lookup: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
|
||||
return m_res;
|
||||
@ -131,11 +176,11 @@ public class Ref<T> : Ref where T : class, new()
|
||||
/// </summary>
|
||||
public override object Lookup(
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0)
|
||||
{
|
||||
return Lookup(reason, dbgName, dbgPath, dbgLine);
|
||||
return Lookup(reason, dbgMethod, dbgPath, dbgLine);
|
||||
}
|
||||
*/
|
||||
|
||||
@ -159,7 +204,7 @@ public class Ref<T> : Ref where T : class, new()
|
||||
public static Ref<T> CreateAsset(
|
||||
T value, string path,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 )
|
||||
{
|
||||
@ -186,7 +231,7 @@ public class Ref<T> : Ref where T : class, new()
|
||||
|
||||
|
||||
var createReason = $"CreateAsset: {value.GetType().Name} - {immMeta?.Reason ?? "N/A"}";
|
||||
var newRef = new Ref<T>( path, $"{createReason} bcs {reason}", dbgName, dbgPath, dbgLine );
|
||||
var newRef = new Ref<T>( path, $"{createReason} bcs {reason}", dbgMethod, dbgPath, dbgLine );
|
||||
|
||||
// We should make the newRef hold the 'value' immediately,
|
||||
// or ensure loading it back gives the same 'value'.
|
||||
@ -209,6 +254,9 @@ internal record ResourceHolder<T>( WeakReference<T> WeakRef, string Name, DateTi
|
||||
/// </summary>
|
||||
public static class Mgr
|
||||
{
|
||||
|
||||
|
||||
|
||||
// Internal holder for type-specific loaders.
|
||||
private abstract class LoadHolder
|
||||
{
|
||||
@ -216,7 +264,7 @@ public static class Mgr
|
||||
// Its not yet working, and maybe shouldnt exist
|
||||
public abstract object Load( string filename,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0
|
||||
);
|
||||
@ -227,7 +275,7 @@ public static class Mgr
|
||||
private readonly Load<T> _fnLoad;
|
||||
public LoadHolder( Load<T> fnLoad ) { _fnLoad = fnLoad; }
|
||||
public override object Load( string filename, string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0
|
||||
) => _fnLoad( filename )!;
|
||||
@ -335,27 +383,26 @@ public static class Mgr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Ref<T> for a given filename.
|
||||
/// </summary>
|
||||
public static Ref<T> Lookup<T>(
|
||||
string filename,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 ) where T : class, new()
|
||||
{
|
||||
return new Ref<T>( filename, reason, dbgName, dbgPath, dbgLine );
|
||||
return new Ref<T>( filename, reason, dbgMethod, dbgPath, dbgLine );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource, handling caching and thread-safe loading.
|
||||
/// </summary>
|
||||
public static T Load<T>(
|
||||
internal static T Load<T>(
|
||||
string filename,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0 ) where T : class, new()
|
||||
{
|
||||
@ -376,8 +423,8 @@ public static class Mgr
|
||||
}
|
||||
|
||||
// 4. Perform the actual load
|
||||
log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgName}:{dbgLine})" );
|
||||
var newValue = ActualLoad<T>( filename, reason, dbgName, dbgPath, dbgLine );
|
||||
log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgMethod}:{dbgLine})" );
|
||||
var newValue = ActualLoad<T>( filename, reason, dbgMethod, dbgPath, dbgLine );
|
||||
|
||||
// 5. Cache the new value
|
||||
CacheResource( filename, newValue, reason );
|
||||
@ -397,7 +444,6 @@ public static class Mgr
|
||||
log.info( $"Cached {typeof( T ).Name}: {filename} ({reason})" );
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve a resource from the cache.
|
||||
/// </summary>
|
||||
@ -422,14 +468,14 @@ public static class Mgr
|
||||
private static T ActualLoad<T>(
|
||||
string filename,
|
||||
string reason = "",
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerMemberName] string dbgMethod = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0
|
||||
) where T : class, new()
|
||||
{
|
||||
if( s_loaders.TryGetValue( typeof( T ), out var loaderHolder ) )
|
||||
{
|
||||
var loadedObject = loaderHolder.Load( filename, reason, dbgName, dbgPath, dbgLine );
|
||||
var loadedObject = loaderHolder.Load( filename, reason, dbgMethod, dbgPath, dbgLine );
|
||||
if( loadedObject is T value )
|
||||
{
|
||||
var meta = ( value as io.Obj )?.Meta;
|
||||
@ -437,7 +483,7 @@ public static class Mgr
|
||||
// If it's an immutable object, record its loading.
|
||||
if( value is io.Obj imm )
|
||||
{
|
||||
return (T)imm.Record( $"Loading bcs {reason}", dbgName, dbgPath, dbgLine );
|
||||
return (T)imm.Record( $"Loading bcs {reason}", dbgMethod, dbgPath, dbgLine );
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -121,7 +121,9 @@ public partial class ObjectHandler : ITypeHandler
|
||||
}
|
||||
|
||||
|
||||
private (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing )
|
||||
|
||||
|
||||
static public (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing )
|
||||
{
|
||||
long id = -1;
|
||||
bool first = true;
|
||||
@ -137,14 +139,7 @@ public partial class ObjectHandler : ITypeHandler
|
||||
object? newObj = null;
|
||||
try
|
||||
{
|
||||
if( type.GetConstructor( Type.EmptyTypes ) != null )
|
||||
{
|
||||
newObj = Activator.CreateInstance( type );
|
||||
}
|
||||
else
|
||||
{
|
||||
newObj = FormatterServices.GetUninitializedObject( type );
|
||||
}
|
||||
newObj = refl.CreateObject( type );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user