Make SharpLib more friendly
This commit is contained in:
parent
3a3a634841
commit
ce168985a8
@ -11,9 +11,12 @@
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>14.0</LangVersion>
|
||||
<Copyright>Copyright 2003..2025 Marc Hernandez</Copyright>
|
||||
<Description>A base set of functionality</Description>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Nullable>disable</Nullable>
|
||||
<Nullability>disable</Nullability>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
|
||||
|
||||
<!--
|
||||
I Want to turn this on, but cant yet. Implementing nullability 1 file at a gime
|
||||
@ -22,6 +25,16 @@
|
||||
<NoWarn>$(NoWarn);SYSLIB0050;CS8981; CS8632</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageReference Include="Optional" Version="4.0.0" />
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNSAFE
|
||||
|
||||
namespace lib
|
||||
{
|
||||
/// <summary>
|
||||
@ -163,3 +165,5 @@ namespace lib
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNSAFE
|
||||
|
||||
110
Utilities.cs
110
Utilities.cs
@ -35,6 +35,7 @@ using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
namespace lib
|
||||
{
|
||||
/// <summary>
|
||||
@ -43,7 +44,6 @@ namespace lib
|
||||
public static class Util
|
||||
{
|
||||
|
||||
/*
|
||||
public static string StringJoin( string separator, IEnumerable<object> collectionToConvert )
|
||||
{
|
||||
return String.Join(separator, collectionToConvert.Select(o => o.ToString()));
|
||||
@ -60,7 +60,9 @@ namespace lib
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
public static string ToString<T>( this IEnumerable<T> collectionToConvert, string separator )
|
||||
@ -79,34 +81,35 @@ namespace lib
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
#if XENKO_PLATFORM_UWP
|
||||
#if XENKO_PLATFORM_UWP
|
||||
public static unsafe void CopyMemory(IntPtr dest, IntPtr src, int sizeInBytesToCopy)
|
||||
{
|
||||
Interop.memcpy((void*)dest, (void*)src, sizeInBytesToCopy);
|
||||
}
|
||||
#else
|
||||
#if XENKO_PLATFORM_WINDOWS_DESKTOP
|
||||
#else
|
||||
#if XENKO_PLATFORM_WINDOWS_DESKTOP
|
||||
private const string MemcpyDll = "msvcrt.dll";
|
||||
#elif XENKO_PLATFORM_ANDROID
|
||||
#elif XENKO_PLATFORM_ANDROID
|
||||
private const string MemcpyDll = "libc.so";
|
||||
#elif XENKO_PLATFORM_UNIX
|
||||
#elif XENKO_PLATFORM_UNIX
|
||||
// We do not specifiy the .so extension as libc.so on Linux
|
||||
// is actually not a .so files but a script. Using just libc
|
||||
// will automatically find the corresponding .so.
|
||||
private const string MemcpyDll = "libc";
|
||||
#elif XENKO_PLATFORM_IOS
|
||||
#elif XENKO_PLATFORM_IOS
|
||||
private const string MemcpyDll = ObjCRuntime.Constants.SystemLibrary;
|
||||
#else
|
||||
# error Unsupported platform
|
||||
#endif
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
[DllImport(MemcpyDll, EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
|
||||
#if !XENKO_RUNTIME_CORECLR
|
||||
#if !XENKO_RUNTIME_CORECLR
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
#endif
|
||||
#endif
|
||||
private static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, ulong sizeInBytesToCopy);
|
||||
|
||||
/// <summary>
|
||||
@ -119,7 +122,7 @@ namespace lib
|
||||
{
|
||||
CopyMemory(dest, src, (ulong)sizeInBytesToCopy);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
*/
|
||||
|
||||
|
||||
@ -139,6 +142,7 @@ namespace lib
|
||||
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// Compares two block of memory.
|
||||
@ -465,6 +469,47 @@ namespace lib
|
||||
Marshal.FreeHGlobal( ( (IntPtr*)alignedBuffer )[-1] );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified T data to a memory location.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of a data to write</typeparam>
|
||||
/// <param name="destination">Memory location to write to.</param>
|
||||
/// <param name="data">The data to write.</param>
|
||||
internal static void UnsafeWrite<T>( IntPtr destination, ref T data )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Interop.CopyInline( (void*)destination, ref data );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified T data from a memory location.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of a data to read</typeparam>
|
||||
/// <param name="source">Memory location to read from.</param>
|
||||
/// <param name="data">The data write to.</param>
|
||||
internal static void UnsafeReadOut<T>( IntPtr source, out T data )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Interop.CopyInlineOut( out data, (void*)source );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the sizeof a struct from a CLR. Equivalent to sizeof operator but works on generics too.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">a struct to evaluate</typeparam>
|
||||
/// <returns>sizeof this struct</returns>
|
||||
internal static int UnsafeSizeOf<T>()
|
||||
{
|
||||
return Interop.SizeOf<T>();
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If non-null, disposes the specified object and set it to null, otherwise do nothing.
|
||||
/// </summary>
|
||||
@ -820,43 +865,6 @@ namespace lib
|
||||
Thread.Sleep( sleepTimeInMillis );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified T data to a memory location.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of a data to write</typeparam>
|
||||
/// <param name="destination">Memory location to write to.</param>
|
||||
/// <param name="data">The data to write.</param>
|
||||
internal static void UnsafeWrite<T>( IntPtr destination, ref T data )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Interop.CopyInline( (void*)destination, ref data );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified T data from a memory location.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of a data to read</typeparam>
|
||||
/// <param name="source">Memory location to read from.</param>
|
||||
/// <param name="data">The data write to.</param>
|
||||
internal static void UnsafeReadOut<T>( IntPtr source, out T data )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Interop.CopyInlineOut( out data, (void*)source );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the sizeof a struct from a CLR. Equivalent to sizeof operator but works on generics too.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">a struct to evaluate</typeparam>
|
||||
/// <returns>sizeof this struct</returns>
|
||||
internal static int UnsafeSizeOf<T>()
|
||||
{
|
||||
return Interop.SizeOf<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linq assisted full tree iteration and collection in a single line.
|
||||
|
||||
@ -5,6 +5,7 @@ using System.Numerics;
|
||||
static public class bit
|
||||
{
|
||||
|
||||
#if UNSAFE
|
||||
public static unsafe TInt ByPointers_DirectInt<TInt, TEnum>( TEnum enumValue )
|
||||
where TInt : IBinaryInteger<TInt>
|
||||
where TEnum : unmanaged, System.Enum
|
||||
@ -14,8 +15,6 @@ static public class bit
|
||||
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
}
|
||||
|
||||
|
||||
|
||||
static public bool On<T, E>( T bitfield, E e )
|
||||
where T : IBinaryInteger<T>
|
||||
where E : unmanaged, System.Enum
|
||||
@ -26,4 +25,19 @@ static public class bit
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
static public bool On<T, E>( T bitfield, E e )
|
||||
where T : IBinaryInteger<T>
|
||||
where E : unmanaged, System.Enum
|
||||
{
|
||||
// FIX FIX FIX
|
||||
// FIX FIX FIX
|
||||
// FIX FIX FIX
|
||||
// FIX FIX FIX
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ namespace lib
|
||||
|
||||
// TODO PERF Make span versions of all these functions
|
||||
|
||||
unsafe public static Id<T> Generate()
|
||||
public static Id<T> Generate()
|
||||
{
|
||||
var buf = new byte[8];
|
||||
|
||||
@ -92,7 +92,7 @@ namespace lib
|
||||
public ser.Types TypesDefault = ser.Types.Fields;
|
||||
}
|
||||
|
||||
public class XmlFormatter2 : IFormatter
|
||||
public class XmlFormatter2
|
||||
{
|
||||
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ using System.Reflection;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using lib.Net;
|
||||
using System.Text;
|
||||
//using System.Threading.Tasks;
|
||||
|
||||
@ -141,14 +140,14 @@ static public class log
|
||||
// It would be better to use a ManualResetEvent here if waiting is truly needed.
|
||||
Thread.Sleep( 500 ); // Simple wait
|
||||
var processId = Process.GetCurrentProcess().Id;
|
||||
LogGC.PrintRuntimeGCEvents( processId );
|
||||
//LogGC.PrintRuntimeGCEvents( processId );
|
||||
}
|
||||
|
||||
static void StartTracing()
|
||||
{
|
||||
// See above comment
|
||||
Thread.Sleep( 500 );
|
||||
Tracing.TraceLogMonitor.Run();
|
||||
//Tracing.TraceLogMonitor.Run();
|
||||
}
|
||||
|
||||
#endregion // CLR Logging
|
||||
@ -825,7 +824,7 @@ static public class log
|
||||
|
||||
StartThread();
|
||||
|
||||
LogGC.RegisterObjectId( s_lock );
|
||||
//LogGC.RegisterObjectId( s_lock );
|
||||
|
||||
info( $"startup END", cat: "log.startup" );
|
||||
}
|
||||
|
||||
@ -87,6 +87,7 @@ namespace math
|
||||
|
||||
Vec3.TransformCoordinate( ref center, ref world, out Center );
|
||||
|
||||
#if UNSAFE
|
||||
// Update world matrix into absolute form
|
||||
unsafe
|
||||
{
|
||||
@ -99,7 +100,7 @@ namespace math
|
||||
++matrixData;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Vec3.TransformNormal( ref extent, ref world, out Extent );
|
||||
}
|
||||
|
||||
|
||||
@ -185,6 +185,7 @@ namespace math
|
||||
return CollisionHelper.SphereContainsSphere( ref this, ref sphere );
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="math.BoundingSphere"/> that fully contains the given points.
|
||||
/// </summary>
|
||||
@ -199,7 +200,6 @@ namespace math
|
||||
FromPoints( (IntPtr)pointsPtr, 0, points.Length, lib.Util.SizeOf<Vec3>(), out result );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="math.BoundingSphere" /> that fully contains the given unmanaged points.
|
||||
/// </summary>
|
||||
@ -251,7 +251,6 @@ namespace math
|
||||
result.Center = center;
|
||||
result.Radius = radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="math.BoundingSphere"/> that fully contains the given points.
|
||||
/// </summary>
|
||||
@ -263,6 +262,7 @@ namespace math
|
||||
FromPoints( points, out result );
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="math.BoundingSphere"/> from a given box.
|
||||
|
||||
@ -1474,13 +1474,23 @@ namespace math
|
||||
return ContainmentType.Contains;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="BoundingFrustum" /> intersects or contains an AABB determined by its center and extent.
|
||||
/// Faster variant specific for frustum culling.
|
||||
/// </summary>
|
||||
/// <param name="frustum">The frustum.</param>
|
||||
/// <param name="boundingBoxExt">The bounding box ext.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="BoundingFrustum" /> intersects or contains an AABB determined by its center and extent.
|
||||
/// Faster variant specific for frustum culling.
|
||||
/// </summary>
|
||||
/// <param name="frustum">The frustum.</param>
|
||||
/// <param name="boundingBoxExt">The bounding box ext.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
||||
|
||||
public static bool FrustumContainsBox( ref BoundingFrustum frustum, ref BoundingBoxExt boundingBoxExt )
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotImplementedException();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#if UNSAFE
|
||||
public static bool FrustumContainsBox( ref BoundingFrustum frustum, ref BoundingBoxExt boundingBoxExt )
|
||||
{
|
||||
unsafe
|
||||
@ -1547,5 +1557,7 @@ namespace math
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,10 +18,12 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Double2 : IEquatable<Double2>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Double2"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Double2>();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Double2"/> with all of its components set to zero.
|
||||
|
||||
@ -17,16 +17,20 @@ namespace math
|
||||
[DataStyle( DataStyle.Compact )]
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Double3 : IEquatable<Double3>, IFormattable
|
||||
{
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Double3"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Double3>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Double3"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Double3 Zero = new Double3();
|
||||
/// <summary>
|
||||
/// A <see cref="math.Double3"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Double3 Zero = new Double3();
|
||||
|
||||
/// <summary>
|
||||
/// The X unit <see cref="math.Double3"/> (1, 0, 0).
|
||||
|
||||
@ -18,15 +18,17 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Double4 : IEquatable<Double4>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Double4"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Double4>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Double4"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Double4 Zero = new Double4();
|
||||
/// <summary>
|
||||
/// A <see cref="math.Double4"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Double4 Zero = new Double4();
|
||||
|
||||
/// <summary>
|
||||
/// The X unit <see cref="math.Double4"/> (1, 0, 0, 0).
|
||||
|
||||
11
math/Int2.cs
11
math/Int2.cs
@ -41,15 +41,18 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Int2 : IEquatable<Int2>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Int2"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Int2>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Int2"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Int2 Zero = new Int2();
|
||||
/// <summary>
|
||||
/// A <see cref="math.Int2"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Int2 Zero = new Int2();
|
||||
|
||||
/// <summary>
|
||||
/// The X unit <see cref="math.Int2"/> (1, 0, 0).
|
||||
|
||||
@ -41,10 +41,13 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Int3 : IEquatable<Int3>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref="Int3"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Int3>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Int3"/> with all of its components set to zero.
|
||||
|
||||
11
math/Int4.cs
11
math/Int4.cs
@ -35,15 +35,18 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Int4 : IEquatable<Int4>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref = "Int4" /> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Int4>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref = "Int4" /> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Int4 Zero = new Int4();
|
||||
/// <summary>
|
||||
/// A <see cref = "Int4" /> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Int4 Zero = new Int4();
|
||||
|
||||
/// <summary>
|
||||
/// The X unit <see cref = "Int4" /> (1, 0, 0, 0).
|
||||
|
||||
@ -79,6 +79,14 @@ namespace math
|
||||
/// The code is using the technique described by Bruce Dawson in
|
||||
/// <a href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">Comparing Floating point numbers 2012 edition</a>.
|
||||
/// </remarks>
|
||||
|
||||
|
||||
public static bool NearEqual( float a, float b )
|
||||
{
|
||||
return Math.Abs( a - b ) < 0.00001f;
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
public static unsafe bool NearEqual( float a, float b )
|
||||
{
|
||||
// Check if the numbers are really close -- needed
|
||||
@ -102,7 +110,7 @@ namespace math
|
||||
const int maxUlp = 4;
|
||||
return ( ulp <= maxUlp );
|
||||
}
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Determines whether the specified value is close to zero (0.0f).
|
||||
/// </summary>
|
||||
|
||||
@ -44,15 +44,18 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Matrix : IEquatable<Matrix>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Matrix"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Matrix>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Matrix"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Matrix Zero = new Matrix();
|
||||
/// <summary>
|
||||
/// A <see cref="math.Matrix"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Matrix Zero = new Matrix();
|
||||
|
||||
/// <summary>
|
||||
/// The identity <see cref="math.Matrix"/>.
|
||||
@ -3168,6 +3171,7 @@ namespace math
|
||||
return result;
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Copies a nxm matrix to this instance.
|
||||
/// </summary>
|
||||
@ -3214,7 +3218,7 @@ namespace math
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Adds two matricies.
|
||||
/// </summary>
|
||||
|
||||
@ -41,15 +41,18 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Quaternion : IEquatable<Quaternion>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Quaternion"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Quaternion>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Quaternion"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Quaternion Zero = new Quaternion();
|
||||
/// <summary>
|
||||
/// A <see cref="math.Quaternion"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
public static readonly Quaternion Zero = new Quaternion();
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="math.Quaternion"/> with all of its components set to one.
|
||||
|
||||
@ -34,10 +34,13 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct UInt4 : IEquatable<UInt4>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// The size of the <see cref = "UInt4" /> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<UInt4>();
|
||||
#endif // UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref = "UInt4" /> with all of its components set to zero.
|
||||
|
||||
@ -42,11 +42,12 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Vec2 : IEquatable<Vec2>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Vec2"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Vec2>();
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// A <see cref="math.Vec2"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
|
||||
@ -43,11 +43,12 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Vec3 : IEquatable<Vec3>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Vec3"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Vec3>();
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// A <see cref="math.Vec3"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
|
||||
@ -42,11 +42,12 @@ namespace math
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 4 )]
|
||||
public struct Vec4 : IEquatable<Vec4>, IFormattable
|
||||
{
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// The size of the <see cref="math.Vec4"/> type, in bytes.
|
||||
/// </summary>
|
||||
public static readonly int SizeInBytes = lib.Util.SizeOf<Vec4>();
|
||||
|
||||
#endif
|
||||
/// <summary>
|
||||
/// A <see cref="math.Vec4"/> with all of its components set to zero.
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
@ -196,7 +196,7 @@ static public class refl
|
||||
return first;
|
||||
}
|
||||
|
||||
LogGC.RegisterObjectId( t );
|
||||
//LogGC.RegisterObjectId( t );
|
||||
|
||||
lock( t )
|
||||
{
|
||||
|
||||
@ -6,7 +6,6 @@ using System.Reflection;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CodeAnalysis; // Note: This was in the original, but seems unused. Keep for now.
|
||||
using System.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#nullable enable
|
||||
|
||||
|
||||
|
||||
#if SCRIPTS
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
@ -20,43 +20,64 @@ using System.Threading;
|
||||
|
||||
public class MemorySourceText : SourceText
|
||||
{
|
||||
public override char this[int position] => ' ';
|
||||
private readonly string _content;
|
||||
|
||||
public MemorySourceText(string content = "")
|
||||
{
|
||||
_content = content ?? string.Empty;
|
||||
}
|
||||
|
||||
public override char this[int position] => _content[position];
|
||||
|
||||
public override Encoding? Encoding => Encoding.UTF8;
|
||||
|
||||
public override int Length => 0;
|
||||
public override int Length => _content.Length;
|
||||
|
||||
public override void CopyTo( int sourceIndex, char[] destination, int destinationIndex, int count )
|
||||
public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
|
||||
{
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
|
||||
if (sourceIndex < 0 || sourceIndex >= _content.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceIndex));
|
||||
|
||||
if (destinationIndex < 0 || destinationIndex >= destination.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(destinationIndex));
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
if (sourceIndex + count > _content.Length || destinationIndex + count > destination.Length)
|
||||
throw new ArgumentException("Source or destination range is out of bounds.");
|
||||
|
||||
_content.CopyTo(sourceIndex, destination, destinationIndex, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class MemoryRefResolver : SourceReferenceResolver
|
||||
{
|
||||
public override bool Equals( object? other )
|
||||
public override bool Equals(object? other)
|
||||
{
|
||||
return false;
|
||||
return other is MemoryRefResolver;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
return typeof(MemoryRefResolver).GetHashCode();
|
||||
}
|
||||
|
||||
private MemoryStream _fakeMS = new MemoryStream();
|
||||
private readonly MemoryStream _fakeMS = new MemoryStream();
|
||||
|
||||
public override string? NormalizePath( string? path, string? baseFilePath ) => path;
|
||||
public override string? NormalizePath(string? path, string? baseFilePath) => path;
|
||||
|
||||
public override Stream OpenRead( string resolvedPath ) => _fakeMS;
|
||||
public override Stream OpenRead(string resolvedPath) => _fakeMS;
|
||||
|
||||
private MemorySourceText _fakeMST = new();
|
||||
private readonly MemorySourceText _fakeMST = new();
|
||||
|
||||
public override SourceText ReadText( string resolvedPath ) => _fakeMST;
|
||||
|
||||
public override string? ResolveReference( string? path, string? baseFilePath ) => path;
|
||||
public override SourceText ReadText(string resolvedPath) => _fakeMST;
|
||||
|
||||
public override string? ResolveReference(string? path, string? baseFilePath) => path;
|
||||
}
|
||||
|
||||
|
||||
@ -204,7 +225,7 @@ public static class scr
|
||||
CompileFile( filename, ( ass ) => { s_fnAss( ass ); }, ( diags ) => { } );
|
||||
}
|
||||
|
||||
public static void CompileFile( string filename, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure, Platform platform = Platform.X86 )
|
||||
public static void CompileFile( string filename, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure/* PORT , Platform platform = Platform.X86*/ )
|
||||
{
|
||||
var fullpath = Path.GetFullPath( filename );
|
||||
|
||||
@ -215,7 +236,7 @@ public static class scr
|
||||
Compile( sourceText, fullpath, onSuccess, onFailure );
|
||||
}
|
||||
|
||||
public static void Compile( string str, string uniquePath, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure, Platform platform = Platform.X86 )
|
||||
public static void Compile( string str, string uniquePath, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure /*, Platform platform = Platform.X86*/ )
|
||||
{
|
||||
var sourceText = SourceText.From( str );
|
||||
|
||||
@ -223,7 +244,7 @@ public static class scr
|
||||
}
|
||||
|
||||
|
||||
public static void Compile( SourceText sourceText, string uniquePath, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure, Platform platform = Platform.X86 )
|
||||
public static void Compile( SourceText sourceText, string uniquePath, Action<Assembly> onSuccess, Action<ImmutableArray<Diagnostic>> onFailure /*, Platform platform = Platform.X86*/ )
|
||||
{
|
||||
string assemblyName = Path.GetRandomFileName();
|
||||
|
||||
@ -237,7 +258,7 @@ public static class scr
|
||||
using MemoryStream ms = new();
|
||||
using MemoryStream pdb = new();
|
||||
|
||||
var result = CompileAndEmit( assemblyName, new[] { syntaxTree }, ms, pdb, platform );
|
||||
var result = CompileAndEmit( assemblyName, new[] { syntaxTree }, ms, pdb );
|
||||
|
||||
if( !result.Success )
|
||||
{
|
||||
@ -269,7 +290,7 @@ public static class scr
|
||||
}
|
||||
}
|
||||
|
||||
private static EmitResult CompileAndEmit( string assemblyName, SyntaxTree[] syntaxTrees, MemoryStream ms, MemoryStream pdb, Platform platform )
|
||||
private static EmitResult CompileAndEmit( string assemblyName, SyntaxTree[] syntaxTrees, MemoryStream ms, MemoryStream pdb /*, Platform platform*/ )
|
||||
{
|
||||
MemoryRefResolver memRef = new();
|
||||
|
||||
@ -281,7 +302,7 @@ public static class scr
|
||||
options: new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary,
|
||||
sourceReferenceResolver: memRef,
|
||||
optimizationLevel: OptimizationLevel.Release,
|
||||
platform: platform,
|
||||
//platform: platform,
|
||||
specificDiagnosticOptions: new Dictionary<string, ReportDiagnostic>
|
||||
{
|
||||
{ "CS1701", ReportDiagnostic.Suppress }
|
||||
@ -358,3 +379,4 @@ public static class scr
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
838
ser/XmlSer.cs
Normal file
838
ser/XmlSer.cs
Normal file
@ -0,0 +1,838 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using System.Net.Sockets;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace ser;
|
||||
|
||||
#region Attributes & Enums (Mostly unchanged, ensure these exist)
|
||||
|
||||
/*
|
||||
|
||||
public CoolClass
|
||||
{
|
||||
private string Rare = "Rare Change";
|
||||
private float TestF = 1.0f;
|
||||
}
|
||||
|
||||
public class Bag
|
||||
{
|
||||
public string Owner { get; set; }
|
||||
public CoolClass Cool { get; set; } = new();
|
||||
}
|
||||
public class BasicTest
|
||||
{
|
||||
public Bag MyBag { get; set; } = new();
|
||||
}
|
||||
|
||||
<root>
|
||||
<MyBag Owner="John" Cool.Rare="Changed" >
|
||||
<Cool TestF="2.5" />
|
||||
</MyBag>
|
||||
</root>
|
||||
|
||||
|
||||
|
||||
public abstract class Item
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
public class Key : Item
|
||||
{
|
||||
public int Code { get; set; }
|
||||
}
|
||||
|
||||
public class Orb : Item
|
||||
{
|
||||
private float Power = 1.0f;
|
||||
public bool IsDark = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public interface I_Serialize
|
||||
{
|
||||
void OnSerialize() { }
|
||||
object OnDeserialize( object enclosing ) => this;
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum Types
|
||||
{
|
||||
Fields = 0b_0001,
|
||||
Props = 0b_0010,
|
||||
Implied = 0b_0100,
|
||||
Explicit = 0b_1000,
|
||||
|
||||
|
||||
None = 0b_0000,
|
||||
Default = Fields,
|
||||
All = Fields | Props,
|
||||
}
|
||||
|
||||
public class Ser : Attribute
|
||||
{
|
||||
public Types Types { get; set; } = Types.Default;
|
||||
}
|
||||
|
||||
public class Do : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
public class Dont : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public class ChildAttribute : Attribute
|
||||
{
|
||||
public string[] Values { get; private set; }
|
||||
|
||||
public ChildAttribute( params string[] values )
|
||||
{
|
||||
this.Values = values;
|
||||
}
|
||||
}
|
||||
|
||||
public class ChildFieldsAttribute : ChildAttribute
|
||||
{
|
||||
public ChildFieldsAttribute( params string[] values ) : base( values ) { }
|
||||
}
|
||||
|
||||
public class ChildPropsAttribute : ChildAttribute
|
||||
{
|
||||
public ChildPropsAttribute( params string[] values ) : base( values ) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface ITypeHandler
|
||||
{
|
||||
bool CanHandle( TypeInfo typeInfo, XmlElement? elem = null ); // Elem needed for Deser
|
||||
void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType );
|
||||
object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Enums & Records (Slightly adjusted/renamed) ---
|
||||
public enum Datastructure { Tree, Graph }
|
||||
public enum BackingFieldNaming { Short, Regular }
|
||||
public enum POD { Attributes, Elements }
|
||||
public record struct TypeProxy( Func<object, string> fnSer, Func<string, string, object> fnDes );
|
||||
|
||||
public record XmlCfg : io.Recorded<XmlCfg>
|
||||
{
|
||||
public bool Verbose { get; init; } = false;
|
||||
public Datastructure Structure { get; init; } = Datastructure.Tree;
|
||||
public int Version { get; init; } = 2;
|
||||
public ImmutableDictionary<Type, TypeProxy> Proxies { get; init; } = ImmutableDictionary<Type, TypeProxy>.Empty;
|
||||
public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Short;
|
||||
public POD POD { get; init; } = POD.Attributes;
|
||||
public ser.Types TypesDefault { get; init; } = ser.Types.Fields;
|
||||
public static XmlCfg Default { get; } = new XmlCfg();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reflection & Metadata Cache
|
||||
|
||||
public record DependentMember(
|
||||
string Name, //Prop or field name
|
||||
TypeInfo Enclosing,
|
||||
MemberInfo EnclosingMember
|
||||
);
|
||||
|
||||
public enum MemberMetaType
|
||||
{
|
||||
Invalid,
|
||||
Simple,
|
||||
Composite,
|
||||
}
|
||||
|
||||
public record MemberMeta(
|
||||
//Base type.
|
||||
Type Type,
|
||||
MemberInfo Info,
|
||||
string XmlName,
|
||||
Func<object, object?> GetValue,
|
||||
Action<object, object?> SetValue,
|
||||
bool IsPodAttribute,
|
||||
bool HasDo,
|
||||
bool HasDont
|
||||
);
|
||||
|
||||
public record TypeInfo(
|
||||
Type Type,
|
||||
List<MemberMeta> Members,
|
||||
bool IsISerializable,
|
||||
bool IsImm,
|
||||
bool IsProxy,
|
||||
TypeProxy? ProxyDef
|
||||
);
|
||||
|
||||
public class TypeMetaCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, TypeInfo> _cache = new();
|
||||
private readonly XmlCfg _cfg;
|
||||
|
||||
public TypeMetaCache( XmlCfg cfg ) => _cfg = cfg;
|
||||
|
||||
public TypeInfo Get( Type type )
|
||||
{
|
||||
// Expanded in anticpation of more complex ways to specify saves/loads
|
||||
|
||||
if( _cache.TryGetValue( type, out var ti ) )
|
||||
return ti;
|
||||
|
||||
var children = new HashSet<string>();
|
||||
|
||||
var tiNew = BuildTypeInfo( type, children );
|
||||
_cache.AddOrUpdate( type, tiNew, ( t, ti ) => tiNew );
|
||||
|
||||
return tiNew;
|
||||
}
|
||||
|
||||
// Helper to create accessors (using standard reflection - can be optimized)
|
||||
private static Func<object, object?> CreateGetter( MemberInfo mi )
|
||||
{
|
||||
if( mi is FieldInfo fi )
|
||||
return fi.GetValue;
|
||||
if( mi is PropertyInfo pi && pi.CanRead )
|
||||
return pi.GetValue;
|
||||
return _ => null;
|
||||
}
|
||||
|
||||
private static Action<object, object?> CreateSetter( MemberInfo mi )
|
||||
{
|
||||
if( mi is FieldInfo fi )
|
||||
return fi.SetValue;
|
||||
if( mi is PropertyInfo pi && pi.CanWrite )
|
||||
return pi.SetValue;
|
||||
return ( _, _ ) => { };
|
||||
}
|
||||
|
||||
|
||||
// Helper to create accessors (using standard reflection - can be optimized)
|
||||
private static Func<object, object?> CreateGetter( MemberInfo mi, Func<object, object?> getter )
|
||||
{
|
||||
if( mi is FieldInfo fi )
|
||||
{
|
||||
var innerGet = fi.GetValue;
|
||||
|
||||
return obj => innerGet( getter( obj ) );
|
||||
}
|
||||
|
||||
if( mi is PropertyInfo pi && pi.CanRead )
|
||||
{
|
||||
Func<object, object?> innerGet = pi.GetValue;
|
||||
|
||||
return obj => innerGet( getter( obj ) );
|
||||
}
|
||||
|
||||
// return pi.GetValue;
|
||||
|
||||
|
||||
return _ => null;
|
||||
}
|
||||
|
||||
private static Action<object, object?> CreateSetter( MemberInfo mi, Func<object, object?> getter )
|
||||
{
|
||||
if( mi is FieldInfo fi )
|
||||
{
|
||||
Action<object, object?> innerSet = fi.SetValue;
|
||||
|
||||
return ( obj, value ) => innerSet( getter( obj ), value );
|
||||
}
|
||||
|
||||
|
||||
if( mi is PropertyInfo pi && pi.CanWrite )
|
||||
{
|
||||
Action<object, object?> innerSet = pi.SetValue;
|
||||
|
||||
//return innerSet;
|
||||
|
||||
//var innerSetType = innerSet.GetType();
|
||||
//var isArgs = innerSetType.Metho
|
||||
|
||||
|
||||
return ( obj, value ) =>
|
||||
{
|
||||
var leaf = getter( obj );
|
||||
innerSet( leaf, value );
|
||||
};
|
||||
}
|
||||
|
||||
return ( _, _ ) => { };
|
||||
}
|
||||
|
||||
|
||||
public void AddType( Type type, params string[] children )
|
||||
{
|
||||
var hashChildren = new HashSet<string>( children );
|
||||
|
||||
BuildTypeInfo( type, hashChildren );
|
||||
}
|
||||
|
||||
private TypeInfo BuildTypeInfo( Type type, HashSet<string> children )
|
||||
{
|
||||
if( _cfg.Verbose )
|
||||
log.info( $"Building TypeInfo for {type.Name}" );
|
||||
|
||||
var members = new List<MemberMeta>();
|
||||
bool doImpls, doFields, doProps;
|
||||
|
||||
GetFilters( _cfg.TypesDefault, type, out doImpls, out doFields, out doProps );
|
||||
|
||||
|
||||
var isImm = typeof( io.Obj ).IsAssignableFrom( type );
|
||||
|
||||
var typesAtt = type.GetCustomAttribute<ser.Ser>( true );
|
||||
var serTypes = typesAtt?.Types ?? ser.Types.None;
|
||||
|
||||
|
||||
if( doFields || doImpls )
|
||||
{
|
||||
foreach( var fi in refl.GetAllFields( type ) )
|
||||
{
|
||||
ProcessMember( fi, serTypes.HasFlag( ser.Types.Fields ), children, doImpls, isImm, members );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if( doProps || doImpls )
|
||||
{
|
||||
foreach( var pi in refl.GetAllProperties( type ) )
|
||||
{
|
||||
ProcessMember( pi, serTypes.HasFlag( ser.Types.Props ), children, doImpls, isImm, members );
|
||||
}
|
||||
}
|
||||
|
||||
var (isProxy, proxyDef) = FindProxy( type );
|
||||
|
||||
//Dont need to sort since we just run through the attributes first, then the nodes
|
||||
//members.Sort( ( a, b ) => ( a.IsPodAttribute.Int < b.IsPodAttribute.Int ) ? 1 : -1);
|
||||
|
||||
return new TypeInfo(
|
||||
type,
|
||||
members,
|
||||
typeof( ISerializable ).IsAssignableFrom( type ) && !typeof( Delegate ).IsAssignableFrom( type ), // Exclude Delegates
|
||||
isImm,
|
||||
isProxy,
|
||||
proxyDef
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private (bool, TypeProxy?) FindProxy( Type type )
|
||||
{
|
||||
var tryType = type;
|
||||
while( tryType != null && tryType != typeof( object ) )
|
||||
{
|
||||
if( _cfg.Proxies.TryGetValue( tryType, out var proxy ) )
|
||||
{
|
||||
return (true, proxy);
|
||||
}
|
||||
tryType = tryType.BaseType;
|
||||
}
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void ProcessMember( MemberInfo mi, bool doMemberType, HashSet<string> childrenOverridden, bool doImpls, bool isImm, List<MemberMeta> members )
|
||||
{
|
||||
List<string> children = new( childrenOverridden );
|
||||
var (hasDo, hasDont, hasImpl, propName, serTypess) = GetMemberAttributes( mi, out var actualMiForAtts, children );
|
||||
|
||||
if( hasDont )
|
||||
return;
|
||||
|
||||
// TODO MH Change this to a configurable query(s)
|
||||
if( isImm && ( mi.Name == "MetaStorage" || mi.Name == "Fn" ) )
|
||||
return;
|
||||
|
||||
|
||||
if( mi.GetCustomAttribute<NonSerializedAttribute>( true ) != null )
|
||||
return;
|
||||
|
||||
if( !(
|
||||
hasDo |
|
||||
doMemberType |
|
||||
( hasImpl & children.Any() )
|
||||
) )
|
||||
return;
|
||||
|
||||
|
||||
var miType = ( mi is FieldInfo fi ) ? fi.FieldType : ( (PropertyInfo)mi ).PropertyType; // CHANGED (moved up)
|
||||
|
||||
string name = mi.Name;
|
||||
string finalName = name;
|
||||
|
||||
if( !string.IsNullOrEmpty( propName ) )
|
||||
{
|
||||
finalName = ( _cfg.Naming == BackingFieldNaming.Short ) ? propName : name;
|
||||
}
|
||||
|
||||
finalName = refl.TypeToIdentifier( finalName ); // Ensure XML-safe name
|
||||
|
||||
var overiddenName = false;
|
||||
|
||||
var getter = CreateGetter( mi );
|
||||
var setter = CreateSetter( mi );
|
||||
|
||||
var blankHashSet = new HashSet<string>();
|
||||
|
||||
if( hasImpl && children.Any() )
|
||||
{
|
||||
//List<MemberMeta> specialMembers = new();
|
||||
foreach( var childName in children )
|
||||
{
|
||||
var memberInfoArr = miType.GetMember( childName );
|
||||
var miFinal = memberInfoArr?.FirstOrDefault();
|
||||
if( miFinal == null )
|
||||
continue;
|
||||
|
||||
var dependentType = miFinal is FieldInfo fidd ? fidd.FieldType : ( miFinal as PropertyInfo ).PropertyType;
|
||||
|
||||
|
||||
bool isPod = Type.GetTypeCode( dependentType ) != TypeCode.Object;
|
||||
|
||||
//ProcessMember( miFinal, blankHashSet, doImpls, isImm, specialMembers );
|
||||
|
||||
//First this one. We need the old getter for the setter.
|
||||
setter = CreateSetter( miFinal, getter );
|
||||
|
||||
//Now wrap the getter itself
|
||||
getter = CreateGetter( miFinal, getter );
|
||||
|
||||
var depName = $"{finalName}.{childName}";
|
||||
|
||||
var memberMeta = new MemberMeta(
|
||||
dependentType,
|
||||
miFinal,
|
||||
depName,
|
||||
getter,
|
||||
setter,
|
||||
isPod && _cfg.POD == POD.Attributes,
|
||||
hasDo,
|
||||
hasDont
|
||||
);
|
||||
|
||||
members.Add( memberMeta );
|
||||
|
||||
if( _cfg.Verbose )
|
||||
{
|
||||
log.info( $"{depName} ({mi.Name}) -> {finalName} ({dependentType.Name}) PodAtt: {isPod && _cfg.POD == POD.Attributes}" );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return;
|
||||
|
||||
/*
|
||||
foreach( var childName in children )
|
||||
{
|
||||
var memberInfoArr = miType.GetMember( childName );
|
||||
var miFinal = memberInfoArr?.FirstOrDefault();
|
||||
if( miFinal != null )
|
||||
{
|
||||
realMemberType = ( miFinal is FieldInfo fin ) ? fin.FieldType : ( miFinal as PropertyInfo ).PropertyType;
|
||||
|
||||
getter = CreateGetter( miFinal, getter );
|
||||
setter = CreateSetter( miFinal, setter );
|
||||
}
|
||||
}
|
||||
//*/
|
||||
}
|
||||
|
||||
{
|
||||
// Simplified POD check
|
||||
bool isPod = Type.GetTypeCode( miType ) != TypeCode.Object && !typeof( IEnumerable ).IsAssignableFrom( miType ) || overiddenName;
|
||||
|
||||
members.Add( new MemberMeta(
|
||||
miType,
|
||||
mi,
|
||||
finalName,
|
||||
getter,
|
||||
setter,
|
||||
isPod && _cfg.POD == POD.Attributes,
|
||||
hasDo,
|
||||
hasDont
|
||||
) );
|
||||
|
||||
if( _cfg.Verbose )
|
||||
{
|
||||
log.info( $"{mi.Name} ({miType.Name}) -> {finalName} ({miType.Name}) PodAtt: {isPod && _cfg.POD == POD.Attributes}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProcessDepedentMember( Type type, HashSet<string> children, List<MemberMeta> members )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
private (bool hasDo, bool hasDont, bool hasImpl, string propName, ser.Types serTypess) GetMemberAttributes( MemberInfo mi, out MemberInfo actualMi, List<string> children )
|
||||
{
|
||||
actualMi = mi;
|
||||
string propName = "";
|
||||
bool isBacking = mi.Name.StartsWith( "<" ) && mi.Name.EndsWith( "BackingField" );
|
||||
|
||||
var typesAtt = mi.DeclaringType.GetCustomAttribute<ser.Ser>( true );
|
||||
|
||||
var serTypes = typesAtt?.Types ?? ser.Types.None;
|
||||
|
||||
var doImpls = serTypes.HasFlag( ser.Types.Implied );
|
||||
|
||||
var attDo = actualMi.GetCustomAttribute<ser.Do>() != null;
|
||||
var attDont = actualMi.GetCustomAttribute<ser.Dont>() != null;
|
||||
|
||||
|
||||
|
||||
if( isBacking && mi is FieldInfo )
|
||||
{
|
||||
var gtIndex = mi.Name.IndexOf( '>' );
|
||||
propName = mi.Name.Substring( 1, gtIndex - 1 );
|
||||
var propInfo = mi.DeclaringType?.GetProperty( propName );
|
||||
if( propInfo != null )
|
||||
actualMi = propInfo;
|
||||
}
|
||||
|
||||
var attChildren = actualMi.GetCustomAttribute<ser.ChildAttribute>();
|
||||
if( attChildren != null )
|
||||
{
|
||||
children.AddRange( attChildren.Values );
|
||||
}
|
||||
|
||||
return (
|
||||
attDo,
|
||||
attDont,
|
||||
doImpls,
|
||||
propName,
|
||||
|
||||
serTypes
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// --- These helpers are copied/adapted from XmlFormatter2 ---
|
||||
|
||||
private static void GetFilters( ser.Types typesDefault, Type type, out bool doImpls, out bool doFields, out bool doProps )
|
||||
{
|
||||
var typesTodo = type.GetCustomAttribute<ser.Ser>( true )?.Types ?? typesDefault;
|
||||
|
||||
doImpls = typesTodo.HasFlag( ser.Types.Implied );
|
||||
doFields = typesTodo.HasFlag( ser.Types.Fields );
|
||||
doProps = typesTodo.HasFlag( ser.Types.Props );
|
||||
}
|
||||
}
|
||||
|
||||
public class TypeResolver
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Type?> _cache = new();
|
||||
private readonly Assembly[] _assemblies;
|
||||
private static readonly FormatterConverter _conv = new();
|
||||
|
||||
public TypeResolver()
|
||||
{
|
||||
_assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
}
|
||||
|
||||
public Type Resolve( XmlElement elem, Type? expectedType )
|
||||
{
|
||||
if( elem.HasAttribute( "_.t" ) )
|
||||
{
|
||||
var typeName = elem.GetAttribute( "_.t" );
|
||||
var resolved = FindType( typeName );
|
||||
if( resolved != null )
|
||||
return resolved;
|
||||
}
|
||||
return expectedType ?? typeof( object ); // Fallback needed
|
||||
}
|
||||
|
||||
public Type? FindType( string typeName )
|
||||
{
|
||||
return _cache.GetOrAdd( typeName, tn =>
|
||||
{
|
||||
// Try direct lookup first (might work for fully qualified)
|
||||
var t = Type.GetType( tn );
|
||||
if( t != null )
|
||||
return t;
|
||||
|
||||
// Then search assemblies
|
||||
foreach( Assembly a in _assemblies )
|
||||
{
|
||||
t = a.GetType( tn );
|
||||
if( t != null )
|
||||
return t;
|
||||
}
|
||||
log.warn( $"Could not resolve type: {tn}" );
|
||||
return null;
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
public object ConvertSimple( string value, Type type )
|
||||
{
|
||||
if( type.IsEnum )
|
||||
return Enum.Parse( type, value );
|
||||
try
|
||||
{
|
||||
return _conv.Convert( value, type );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
object defaultVal = type.IsValueType ? Activator.CreateInstance( type )! : null!;
|
||||
log.warn( $"Conversion failed for '{value}' to {type.Name}: {ex.Message}. Returning default of {defaultVal}({defaultVal.GetType().Name})." );
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XmlSer (Coordinator)
|
||||
|
||||
public class XmlSer // : IFormatter
|
||||
{
|
||||
internal readonly XmlCfg _cfg;
|
||||
internal readonly TypeMetaCache _meta;
|
||||
internal readonly TypeResolver _resolver;
|
||||
private readonly List<ITypeHandler> _handlers;
|
||||
|
||||
// Per-operation state
|
||||
internal ObjectIDGenerator _idGen = new();
|
||||
internal Dictionary<long, object> _processed = new();
|
||||
private string _streamSource = "";
|
||||
|
||||
public XmlSer( XmlCfg? cfg = null, TypeMetaCache metaCache = null )
|
||||
{
|
||||
var isCustomConfig = cfg != null;
|
||||
|
||||
_cfg = cfg ?? XmlCfg.Default;
|
||||
|
||||
if( _cfg.Verbose )
|
||||
{
|
||||
log.info( $"Config:" );
|
||||
log.info( $" {log.var( _cfg.Verbose )}" );
|
||||
log.info( $" {log.var( _cfg.Structure )}" );
|
||||
log.info( $" {log.var( _cfg.Version )}" );
|
||||
log.info( $" {log.var( _cfg.Naming )}" );
|
||||
log.info( $" {log.var( _cfg.POD )}" );
|
||||
log.info( $" {log.var( _cfg.TypesDefault )}" );
|
||||
}
|
||||
|
||||
|
||||
_meta = metaCache ?? new TypeMetaCache( _cfg );
|
||||
|
||||
_resolver = new TypeResolver();
|
||||
|
||||
_handlers = new List<ITypeHandler>
|
||||
{
|
||||
new ProxyHandler(),
|
||||
new ISerializableHandler(),
|
||||
new PrimitiveHandler(),
|
||||
new CollectionHandler(),
|
||||
new ObjectHandler() // Must be last
|
||||
};
|
||||
|
||||
if( _cfg.Verbose )
|
||||
{
|
||||
log.info( $"Handlers in importance..." );
|
||||
foreach( var h in _handlers )
|
||||
{
|
||||
log.info( $" {h.GetType().Name}" );
|
||||
}
|
||||
|
||||
log.high( "XmlSer Initialized." );
|
||||
}
|
||||
}
|
||||
|
||||
internal ITypeHandler GetHandler( Type t, XmlElement? elem = null )
|
||||
{
|
||||
var ti = _meta.Get( t );
|
||||
return _handlers.First( h => h.CanHandle( ti, elem ) );
|
||||
}
|
||||
|
||||
// --- Context Helpers ---
|
||||
internal void WriteTypeAttr( XmlWriter writer, Type memberType, Type actualType )
|
||||
{
|
||||
if( memberType != actualType )
|
||||
{
|
||||
writer.WriteAttributeString( "_.t", actualType.FullName );
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HandleGraphWrite( XmlWriter writer, object obj, out bool first )
|
||||
{
|
||||
first = true;
|
||||
if( _cfg.Structure == Datastructure.Graph )
|
||||
{
|
||||
long id = _idGen.GetId( obj, out first );
|
||||
writer.WriteAttributeString( "ref", id.ToString() );
|
||||
if( first )
|
||||
_processed[id] = obj;
|
||||
}
|
||||
return first || _cfg.Structure == Datastructure.Tree; // Write if first or if Tree
|
||||
}
|
||||
|
||||
internal long TrackIfGraph( object obj, XmlElement elem )
|
||||
{
|
||||
long id = -1;
|
||||
bool first;
|
||||
if( _cfg.Structure == Datastructure.Graph )
|
||||
{
|
||||
id = _idGen.GetId( obj, out first );
|
||||
if( elem.HasAttribute( "ref" ) )
|
||||
{
|
||||
id = long.Parse( elem.GetAttribute( "ref" ) );
|
||||
}
|
||||
if( !_processed.ContainsKey( id ) )
|
||||
{
|
||||
_processed[id] = obj;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
// --- Deserialization ---
|
||||
public T? Deserialize<T>( Stream stream ) => (T?)Deserialize( stream, typeof( T ) );
|
||||
|
||||
public object? Deserialize( Stream stream, Type? type = null )
|
||||
{
|
||||
_streamSource = stream.ToString() ?? "{null}"; // Basic source, improve as needed
|
||||
_processed.Clear();
|
||||
_idGen = new ObjectIDGenerator();
|
||||
|
||||
using var reader = XmlReader.Create( stream, new XmlReaderSettings { IgnoreWhitespace = true } );
|
||||
XmlDocument doc = new XmlDocument();
|
||||
try
|
||||
{ doc.Load( reader ); }
|
||||
catch( Exception ex ) { log.exception( ex, $"XML Load failed: {ex.Message}" ); return null; }
|
||||
|
||||
if( doc.DocumentElement == null )
|
||||
return null;
|
||||
|
||||
return ReadNode( doc.DocumentElement, type ?? typeof( object ), null );
|
||||
}
|
||||
|
||||
public void DeserializeInto<T>( Stream stream, T obj ) where T : class
|
||||
{
|
||||
_streamSource = stream.ToString() ?? "{null}";
|
||||
_processed.Clear();
|
||||
_idGen = new ObjectIDGenerator();
|
||||
|
||||
using var reader = XmlReader.Create( stream, new XmlReaderSettings { IgnoreWhitespace = true } );
|
||||
XmlDocument doc = new XmlDocument();
|
||||
try
|
||||
{
|
||||
doc.Load( reader );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"XML Load failed: {ex.Message}" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( doc.DocumentElement == null )
|
||||
return;
|
||||
|
||||
ReadNode( doc.DocumentElement, typeof( T ), obj );
|
||||
}
|
||||
|
||||
internal object? ReadNode( XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
if( elem.HasAttribute( "v" ) && elem.GetAttribute( "v" ) == "null" )
|
||||
return null;
|
||||
|
||||
// 1. Handle refs (if Graph)
|
||||
if( _cfg.Structure == Datastructure.Graph && elem.HasAttribute( "ref" ) )
|
||||
{
|
||||
long id = long.Parse( elem.GetAttribute( "ref" ) );
|
||||
if( _processed.TryGetValue( id, out var obj ) )
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 2. Determine Type & Select Handler
|
||||
var actualType = _resolver.Resolve( elem, expectedType );
|
||||
var ti = _meta.Get( actualType );
|
||||
var handler = _handlers.First( h => h.CanHandle( ti, elem ) );
|
||||
|
||||
// 3. Delegate
|
||||
return handler.ReadXml( this, elem, actualType, existing );
|
||||
}
|
||||
|
||||
// --- Serialization ---
|
||||
public void Serialize( Stream stream, object root )
|
||||
{
|
||||
_processed.Clear();
|
||||
_idGen = new ObjectIDGenerator();
|
||||
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
Encoding = System.Text.Encoding.UTF8, // Use UTF8 for better compatibility
|
||||
OmitXmlDeclaration = true // Often preferred for fragments/storage
|
||||
};
|
||||
|
||||
using var writer = XmlWriter.Create( stream, settings );
|
||||
|
||||
writer.WriteStartDocument();
|
||||
WriteNode( writer, root, "root", root?.GetType() ?? typeof( object ), true ); // Force type on root
|
||||
writer.WriteEndDocument();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
internal void WriteNode( XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( _cfg.Verbose )
|
||||
log.info( $"Writing {name} ({memberType}) force: {forceType}" );
|
||||
|
||||
if( obj == null )
|
||||
{
|
||||
writer.WriteStartElement( name );
|
||||
writer.WriteAttributeString( "v", "null" );
|
||||
writer.WriteEndElement();
|
||||
return;
|
||||
}
|
||||
|
||||
var actualType = obj.GetType();
|
||||
var ti = _meta.Get( actualType );
|
||||
var handler = _handlers.First( h => h.CanHandle( ti ) );
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
handler.WriteXml( this, writer, obj, name, memberType, forceType || memberType != actualType );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"{name}({memberType.Name}) forceType: {forceType}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
159
ser/XmlSer_Core.cs
Normal file
159
ser/XmlSer_Core.cs
Normal file
@ -0,0 +1,159 @@
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml;
|
||||
|
||||
namespace ser;
|
||||
|
||||
|
||||
// --- Primitive Handler ---
|
||||
public partial class PrimitiveHandler : ser.ITypeHandler
|
||||
{
|
||||
public bool CanHandle( TypeInfo ti, XmlElement? elem )
|
||||
{
|
||||
var typeCode = Type.GetTypeCode( ti.Type );
|
||||
var typeNotObject = Type.GetTypeCode( ti.Type ) != TypeCode.Object;
|
||||
//var isString = ti.Type == typeof( string );
|
||||
return typeNotObject;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Proxy Handler ---
|
||||
public partial class ProxyHandler : ITypeHandler
|
||||
{
|
||||
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => ti.IsProxy || ( elem?.HasAttribute( "proxy" ) ?? false );
|
||||
}
|
||||
|
||||
// --- ISerializable Handler ---
|
||||
public partial class ISerializableHandler : ITypeHandler
|
||||
{
|
||||
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => ti.IsISerializable;
|
||||
}
|
||||
|
||||
// --- Collection Handler ---
|
||||
public partial class CollectionHandler : ITypeHandler
|
||||
{
|
||||
public bool CanHandle( TypeInfo ti, XmlElement? elem ) =>
|
||||
typeof( IEnumerable ).IsAssignableFrom( ti.Type );
|
||||
|
||||
private Type GetElementType( Type collectionType )
|
||||
{
|
||||
if( collectionType.IsArray )
|
||||
return collectionType.GetElementType()!;
|
||||
if( collectionType.IsGenericType )
|
||||
{
|
||||
var args = collectionType.GetGenericArguments();
|
||||
if( args.Length == 1 )
|
||||
return args[0];
|
||||
if( args.Length == 2 )
|
||||
return typeof( KeyValuePair<,> ).MakeGenericType( args );
|
||||
}
|
||||
return typeof( object ); // Fallback
|
||||
}
|
||||
|
||||
private object ConvertToFinalCollection( IList list, Type expectedType, Type elemType )
|
||||
{
|
||||
if( expectedType.IsArray )
|
||||
{
|
||||
var arr = Array.CreateInstance( elemType, list.Count );
|
||||
list.CopyTo( arr, 0 );
|
||||
return arr;
|
||||
}
|
||||
if( expectedType.IsGenericType )
|
||||
{
|
||||
var genDef = expectedType.GetGenericTypeDefinition();
|
||||
if( genDef == typeof( ImmutableArray<> ) )
|
||||
{
|
||||
var method = typeof( ImmutableArray ).GetMethods()
|
||||
.First( m => m.Name == "ToImmutableArray" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
|
||||
.MakeGenericMethod( elemType );
|
||||
return method.Invoke( null, new object[] { list } )!;
|
||||
}
|
||||
if( genDef == typeof( ImmutableList<> ) )
|
||||
{
|
||||
var method = typeof( ImmutableList ).GetMethods()
|
||||
.First( m => m.Name == "ToImmutableList" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
|
||||
.MakeGenericMethod( elemType );
|
||||
return method.Invoke( null, new object[] { list } )!;
|
||||
}
|
||||
// Add more immutable/dictionary handlers here (using MakeImmutableDictionary etc.)
|
||||
}
|
||||
return list; // Default to List<T> if no specific match
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Object Handler (Default/Complex) ---
|
||||
public partial class ObjectHandler : ITypeHandler
|
||||
{
|
||||
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => true; // Fallback
|
||||
|
||||
private (XmlNode? source, bool isAttribute) FindValueSource( XmlElement parent, string name )
|
||||
{
|
||||
if( parent.HasAttribute( name ) )
|
||||
{
|
||||
return (parent.Attributes[name], true);
|
||||
}
|
||||
foreach( XmlNode node in parent.ChildNodes )
|
||||
{
|
||||
if( node.NodeType == XmlNodeType.Element && node.Name == name )
|
||||
{
|
||||
return (node, false);
|
||||
}
|
||||
}
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
private bool ShouldSetValue( MemberMeta member, bool isHydrating )
|
||||
{
|
||||
// [Dont] members are filtered out during metadata generation.
|
||||
// If a member is present in the metadata and a value is found in the XML,
|
||||
// we should always set it. This handles both new creation and hydration/merge.
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing )
|
||||
{
|
||||
long id = -1;
|
||||
bool first = true;
|
||||
|
||||
// Check existing
|
||||
if( existing != null && type.IsAssignableFrom( existing.GetType() ) )
|
||||
{
|
||||
id = xml._idGen.GetId( existing, out first );
|
||||
return (existing, id);
|
||||
}
|
||||
|
||||
// Create new
|
||||
object? newObj = null;
|
||||
try
|
||||
{
|
||||
if( type.GetConstructor( Type.EmptyTypes ) != null )
|
||||
{
|
||||
newObj = Activator.CreateInstance( type );
|
||||
}
|
||||
else
|
||||
{
|
||||
newObj = FormatterServices.GetUninitializedObject( type );
|
||||
}
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"Failed to create instance of {type.Name}: {ex.Message}" );
|
||||
return (null, -1);
|
||||
}
|
||||
|
||||
id = xml._idGen.GetId( newObj, out first );
|
||||
return (newObj, id);
|
||||
}
|
||||
}
|
||||
|
||||
188
ser/XmlSer_Read.cs
Normal file
188
ser/XmlSer_Read.cs
Normal file
@ -0,0 +1,188 @@
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml;
|
||||
|
||||
namespace ser;
|
||||
|
||||
|
||||
// --- Primitive Handler ---
|
||||
public partial class PrimitiveHandler : ser.ITypeHandler
|
||||
{
|
||||
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
string val = elem.HasAttribute( "v" ) ? elem.GetAttribute( "v" ) : elem.InnerText;
|
||||
if( val == "null" )
|
||||
return null;
|
||||
|
||||
// So this is an interesting one. Why not use the expected type? Well, we know we have
|
||||
// data in the XML, it just wont convert to what we want.
|
||||
return xml._resolver.ConvertSimple( val, expectedType );
|
||||
}
|
||||
}
|
||||
|
||||
// --- Proxy Handler ---
|
||||
public partial class ProxyHandler : ITypeHandler
|
||||
{
|
||||
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
var ti = xml._meta.Get( expectedType ); // Re-get to ensure we have proxy info
|
||||
if( !elem.HasAttribute( "proxy" ) || !ti.ProxyDef.HasValue )
|
||||
{
|
||||
log.warn( $"Proxy read failed for {expectedType.Name}. Fallback needed." );
|
||||
return null; // Should fall back or throw
|
||||
}
|
||||
var proxyVal = elem.GetAttribute( "proxy" );
|
||||
return ti.ProxyDef.Value.fnDes( expectedType.FullName, proxyVal );
|
||||
}
|
||||
}
|
||||
|
||||
// --- ISerializable Handler ---
|
||||
public partial class ISerializableHandler : ITypeHandler
|
||||
{
|
||||
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
// Create/Get instance (needs FormatterServices for ISerializable)
|
||||
object obj = existing ?? FormatterServices.GetUninitializedObject( expectedType );
|
||||
long id = xml.TrackIfGraph( obj, elem ); // Track it
|
||||
|
||||
var serInfo = new SerializationInfo( expectedType, new FormatterConverter() );
|
||||
|
||||
foreach( XmlNode objNode in elem.ChildNodes )
|
||||
{
|
||||
if( objNode is XmlElement childElem )
|
||||
{
|
||||
string childName = childElem.Name;
|
||||
Type? childType = xml._resolver.FindType( childElem.GetAttribute( "_.t" ) );
|
||||
if( childType != null )
|
||||
{
|
||||
var desValue = xml.ReadNode( childElem, childType, null );
|
||||
serInfo.AddValue( childName, desValue, childType );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var context = new StreamingContext( StreamingContextStates.All ); // Or use xml.Context
|
||||
var cons = expectedType.GetConstructor(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
null, new[] { typeof( SerializationInfo ), typeof( StreamingContext ) }, null );
|
||||
|
||||
if( cons != null )
|
||||
{
|
||||
cons.Invoke( obj, new object[] { serInfo, context } );
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error( $"ISerializable type {expectedType.Name} lacks the required constructor." );
|
||||
}
|
||||
|
||||
if( obj is IDeserializationCallback cb )
|
||||
cb.OnDeserialization( obj );
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Collection Handler ---
|
||||
public partial class CollectionHandler : ITypeHandler
|
||||
{
|
||||
|
||||
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
// Determine element type
|
||||
Type elemType = GetElementType( expectedType );
|
||||
|
||||
// Create a temporary list
|
||||
var listType = typeof( List<> ).MakeGenericType( elemType );
|
||||
var list = (IList)Activator.CreateInstance( listType )!;
|
||||
|
||||
xml.TrackIfGraph( list, elem ); // Track list if graph
|
||||
|
||||
// Populate the list
|
||||
foreach( XmlNode node in elem.ChildNodes )
|
||||
{
|
||||
if( node is XmlElement childElem )
|
||||
{
|
||||
list.Add( xml.ReadNode( childElem, elemType, null ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to the final expected type (Array, Immutable*, List)
|
||||
return ConvertToFinalCollection( list, expectedType, elemType );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Object Handler (Default/Complex) ---
|
||||
public partial class ObjectHandler : ITypeHandler
|
||||
{
|
||||
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
|
||||
{
|
||||
var actualType = xml._resolver.Resolve( elem, expectedType );
|
||||
var ti = xml._meta.Get( actualType );
|
||||
|
||||
// 1. Get/Create Instance
|
||||
var (obj, _) = GetOrCreateInstance( xml, elem, actualType, existing );
|
||||
if( obj == null )
|
||||
return null;
|
||||
|
||||
// Handle graph refs (if already processed)
|
||||
if( xml._cfg.Structure == Datastructure.Graph && elem.HasAttribute( "ref" ) )
|
||||
{
|
||||
long id = long.Parse( elem.GetAttribute( "ref" ) );
|
||||
if( xml._processed.TryGetValue( id, out var processedObj ) )
|
||||
return processedObj;
|
||||
}
|
||||
|
||||
// Track if it's new
|
||||
xml.TrackIfGraph( obj, elem );
|
||||
|
||||
// 2. Hydrate
|
||||
foreach( var memberMeta in ti.Members )
|
||||
{
|
||||
|
||||
|
||||
|
||||
{
|
||||
var (valueSource, isAttribute) = FindValueSource( elem, memberMeta.XmlName );
|
||||
|
||||
if( valueSource != null )
|
||||
{
|
||||
object? memberValue;
|
||||
object? currentMemberValue = memberMeta.GetValue( obj );
|
||||
|
||||
if( isAttribute )
|
||||
{
|
||||
memberValue = xml._resolver.ConvertSimple( valueSource.Value!, memberMeta.Type );
|
||||
}
|
||||
else // Child Element
|
||||
{
|
||||
memberValue = xml.ReadNode( (XmlElement)valueSource, memberMeta.Type, currentMemberValue );
|
||||
}
|
||||
|
||||
// Set value, respecting ser.Do/ser.Dont and pre-hydration
|
||||
if( ShouldSetValue( memberMeta, existing != null ) )
|
||||
{
|
||||
memberMeta.SetValue( obj, memberValue );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Post-processing
|
||||
if( obj is ser.I_Serialize iSer )
|
||||
obj = iSer.OnDeserialize( null );
|
||||
if( ti.IsImm && obj is io.Obj immObj )
|
||||
return immObj.Record( $"From XML {elem.Name}" );
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
133
ser/XmlSer_Tests.cs
Normal file
133
ser/XmlSer_Tests.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
//using Org.BouncyCastle.Crypto.IO;
|
||||
|
||||
namespace ser;
|
||||
|
||||
|
||||
public record class SimpleImmutable( string Name, int Age ) : io.Timed<SimpleImmutable>;
|
||||
|
||||
static public class Test
|
||||
{
|
||||
|
||||
public class ExternalClass
|
||||
{
|
||||
public string ActualProperty { get; set; } = "test_ActualProperty_set_inline";
|
||||
//public string ActualProperty_NotSerialized { get; set; } = "ActualProperty_NotSerialized";
|
||||
}
|
||||
|
||||
[ser.Ser( Types = ser.Types.Implied )]
|
||||
public partial class LeaveWithExternalClasss
|
||||
{
|
||||
|
||||
//[ser.Do]
|
||||
//public bool _cccwp_doBool = true;
|
||||
|
||||
[ser.ChildPropsAttribute( "ActualProperty" )]
|
||||
public ExternalClass _leaf_external = new();
|
||||
|
||||
//public string _cccwp_doNotSerialize = "test_do_not_serialize";
|
||||
|
||||
}
|
||||
|
||||
[ser.Ser]
|
||||
public class TrunkClass
|
||||
{
|
||||
public LeaveWithExternalClasss _trunk_leaf = new();
|
||||
//public int _chf_test = 10;
|
||||
//private string _chf_priv_string = "test_priv_string";
|
||||
};
|
||||
|
||||
public static void Serialization()
|
||||
{
|
||||
ser.XmlCfg cfg = new()
|
||||
{
|
||||
Verbose = true,
|
||||
};
|
||||
|
||||
ser.TypeMetaCache metaCache = new( cfg );
|
||||
//metaCache.AddType( typeof( ClassContainsClassWithProp ), "ActualProperty" );
|
||||
|
||||
|
||||
|
||||
|
||||
TrunkClass trunk = new()
|
||||
{
|
||||
_trunk_leaf = new()
|
||||
{
|
||||
_leaf_external = new()
|
||||
{
|
||||
ActualProperty = "ActualProperty_set_in_cons"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "ActualProperty_set_in_cons" );
|
||||
|
||||
|
||||
var memStream = new MemoryStream();
|
||||
|
||||
{
|
||||
var xml = new ser.XmlSer( cfg, metaCache );
|
||||
xml.Serialize( memStream, trunk );
|
||||
}
|
||||
|
||||
memStream.Position = 0;
|
||||
|
||||
var strXml = System.Text.Encoding.UTF8.GetString( memStream.ToArray() );
|
||||
|
||||
var badXml = "<root>\n <prop doBool=\"True\" />\n</root>";
|
||||
///*
|
||||
var badXml_02 = @"""<root>
|
||||
<prop doBool=""True"">
|
||||
<propHolder>
|
||||
<ActualProperty ActualProperty=""ActualProperty_set_in_cons"" />
|
||||
<ActualProperty_NotSerialized ActualProperty_NotSerialized=""ActualProperty_NotSerialized"" />
|
||||
</propHolder>
|
||||
<doNotSerialize doNotSerialize=""test_do_not_serialize"" />
|
||||
</prop>
|
||||
</root>""";
|
||||
//*/
|
||||
Debug.Assert( strXml != badXml );
|
||||
|
||||
memStream.Position = 0;
|
||||
|
||||
var classHasFields2 = new TrunkClass();
|
||||
//classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized = "ActualProperty_NotSerialized_set_in_test_01";
|
||||
|
||||
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
|
||||
//Debug.Assert( classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized" );
|
||||
|
||||
|
||||
{
|
||||
var xml = new ser.XmlSer( cfg, metaCache );
|
||||
classHasFields2 = xml.Deserialize<TrunkClass>( memStream );
|
||||
}
|
||||
|
||||
|
||||
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
|
||||
//Debug.Assert( classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
|
||||
|
||||
memStream.Position = 0;
|
||||
|
||||
var classHasFields3 = new TrunkClass();
|
||||
|
||||
{
|
||||
var xml = new ser.XmlSer( cfg, metaCache );
|
||||
xml.DeserializeInto( memStream, classHasFields3 );
|
||||
}
|
||||
|
||||
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
|
||||
//Debug.Assert( classHasFields3._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
201
ser/XmlSer_Write.cs
Normal file
201
ser/XmlSer_Write.cs
Normal file
@ -0,0 +1,201 @@
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml;
|
||||
|
||||
namespace ser;
|
||||
|
||||
|
||||
// --- Primitive Handler ---
|
||||
public partial class PrimitiveHandler : ser.ITypeHandler
|
||||
{
|
||||
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( obj == null )
|
||||
{
|
||||
writer.WriteStartElement( name );
|
||||
writer.WriteAttributeString( "v", "null" );
|
||||
writer.WriteEndElement();
|
||||
return;
|
||||
}
|
||||
|
||||
bool writeElements = xml._cfg.POD == POD.Elements || forceType || !( writer is XmlTextWriter );
|
||||
if( !writeElements && writer is XmlTextWriter tw )
|
||||
writeElements = tw.WriteState != WriteState.Element;
|
||||
|
||||
if( writeElements )
|
||||
writer.WriteStartElement( name );
|
||||
|
||||
if( forceType || xml._cfg.POD == POD.Elements )
|
||||
{
|
||||
if( forceType )
|
||||
writer.WriteAttributeString( "_.t", obj.GetType().FullName );
|
||||
|
||||
writer.WriteAttributeString( "v", obj.ToString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteAttributeString( name, obj.ToString() );
|
||||
}
|
||||
|
||||
if( writeElements )
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Proxy Handler ---
|
||||
public partial class ProxyHandler : ITypeHandler
|
||||
{
|
||||
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( obj == null )
|
||||
{ xml.GetHandler( typeof( object ) ).WriteXml( xml, writer, null, name, memberType, forceType ); return; }
|
||||
|
||||
var ti = xml._meta.Get( obj.GetType() );
|
||||
if( !ti.ProxyDef.HasValue )
|
||||
{ log.error( "Proxy write called without proxy def!" ); return; }
|
||||
|
||||
writer.WriteStartElement( name );
|
||||
var proxyStr = ti.ProxyDef.Value.fnSer( obj );
|
||||
|
||||
// TODO: Allow arbitrary writing here
|
||||
writer.WriteAttributeString( "proxy", proxyStr );
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// --- ISerializable Handler ---
|
||||
public partial class ISerializableHandler : ITypeHandler
|
||||
{
|
||||
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( obj == null )
|
||||
{ /* Write null */ return; }
|
||||
if( !( obj is ISerializable serObj ) )
|
||||
{ /* Error */ return; }
|
||||
|
||||
writer.WriteStartElement( name );
|
||||
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
|
||||
|
||||
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
var serInfo = new SerializationInfo( obj.GetType(), new FormatterConverter() );
|
||||
var context = new StreamingContext( StreamingContextStates.All );
|
||||
serObj.GetObjectData( serInfo, context );
|
||||
|
||||
foreach( var member in serInfo )
|
||||
{
|
||||
xml.WriteNode( writer, member.Value, refl.TypeToIdentifier( member.Name ), member.ObjectType, true ); // Force type for ISer
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Collection Handler ---
|
||||
public partial class CollectionHandler : ITypeHandler
|
||||
{
|
||||
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( obj == null )
|
||||
{ /* Write null */ return; }
|
||||
if( !( obj is IEnumerable collection ) )
|
||||
{ /* Error */ return; }
|
||||
|
||||
writer.WriteStartElement( name );
|
||||
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
|
||||
|
||||
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
Type elemType = GetElementType( obj.GetType() );
|
||||
int i = 0;
|
||||
foreach( var item in collection )
|
||||
{
|
||||
xml.WriteNode( writer, item, $"i{i++}", elemType, false );
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Object Handler (Default/Complex) ---
|
||||
public partial class ObjectHandler : ITypeHandler
|
||||
{
|
||||
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
|
||||
{
|
||||
if( obj == null )
|
||||
{ /* Write null */ return; }
|
||||
|
||||
writer.WriteStartElement( name );
|
||||
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
|
||||
var ti = xml._meta.Get( obj.GetType() );
|
||||
|
||||
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
foreach( var memberMeta in ti.Members )
|
||||
{
|
||||
if( !memberMeta.IsPodAttribute )
|
||||
continue;
|
||||
|
||||
var value = memberMeta.GetValue( obj );
|
||||
if( value != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.WriteAttributeString( memberMeta.XmlName, value.ToString() );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"Writing Att {memberMeta.XmlName} = [{value}]" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach( var memberMeta in ti.Members )
|
||||
{
|
||||
if( memberMeta.IsPodAttribute )
|
||||
continue;
|
||||
|
||||
var value = memberMeta.GetValue( obj );
|
||||
if( value != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
xml.WriteNode( writer, value, memberMeta.XmlName, memberMeta.Type, false );
|
||||
}
|
||||
catch( Exception ex )
|
||||
{
|
||||
log.exception( ex, $"Writing Node {memberMeta.XmlName} = [{value}]" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
25
sharplib.sln
25
sharplib.sln
@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpLib", "SharpLib.csproj", "{CC1801F8-7270-47A2-AF73-CCE2900549A9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CC1801F8-7270-47A2-AF73-CCE2900549A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CC1801F8-7270-47A2-AF73-CCE2900549A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC1801F8-7270-47A2-AF73-CCE2900549A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC1801F8-7270-47A2-AF73-CCE2900549A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E2F905DE-69F3-48F3-943C-B8544945C2E9}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpLib", "SharpLib.csproj", "{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2C2EFBD0-C7A0-4D9C-B6CD-68E0112B6786}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
namespace time;
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace lib;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user