Strip out experimental stuff

This commit is contained in:
Marc Hernandez 2026-02-13 14:08:48 -08:00
parent 40dd96d06e
commit 1e3204601e
54 changed files with 0 additions and 10419 deletions

View File

@ -1,73 +0,0 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using adaptive arithmetic coding.
/// <para>Usage: java AdaptiveArithmeticCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "AdaptiveArithmeticDecompress" application to recreate the original input file.</para>
/// <para>Note that the application starts with a flat frequency table of 257 symbols (all set to a frequency of 1),
/// and updates it after each byte encoded. The corresponding decompressor program also starts with a flat
/// frequency table and updates it after each byte decoded. It is by design that the compressor and
/// decompressor have synchronized states, so that the data can be decompressed properly.</para>
/// </summary>
public class AdaptiveArithmeticCompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java AdaptiveArithmeticCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file compression
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
compress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress( Stream @in, BitOutputStream @out )
{
FlatFrequencyTable initFreqs = new FlatFrequencyTable( 257 );
FrequencyTable freqs = new SimpleFrequencyTable( initFreqs );
ArithmeticEncoder enc = new ArithmeticEncoder( 32, @out );
while( true )
{
// Read and encode one byte
int symbol = @in.ReadByte();
if( symbol == -1 )
{
break;
}
enc.write( freqs, symbol );
freqs.increment( symbol );
}
enc.write( freqs, 256 ); // EOF
enc.finish(); // Flush remaining code bits
}
}

View File

@ -1,67 +0,0 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using adaptive arithmetic coding.
/// <para>Usage: java AdaptiveArithmeticDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "AdaptiveArithmeticCompress" application.</para>
/// </summary>
public class AdaptiveArithmeticDecompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java AdaptiveArithmeticDecompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file decompression
using (BitInputStream @in = new BitInputStream(new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read))), Stream @out = new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write)))
{
decompress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress( BitInputStream @in, Stream @out )
{
FlatFrequencyTable initFreqs = new FlatFrequencyTable( 257 );
FrequencyTable freqs = new SimpleFrequencyTable( initFreqs );
ArithmeticDecoder dec = new ArithmeticDecoder( 32, @in );
while( true )
{
// Decode and write one byte
int symbol = dec.read( freqs );
if( symbol == 256 ) // EOF symbol
{
break;
}
@out.WriteByte( (byte)symbol );
freqs.increment( symbol );
}
}
}

View File

@ -1,185 +0,0 @@
using System;
using System.Diagnostics;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Provides the state and behaviors that arithmetic coding encoders and decoders share. </summary>
/// <seealso cref= ArithmeticEncoder </seealso>
/// <seealso cref= ArithmeticDecoder </seealso>
public abstract class ArithmeticCoderBase
{
/*---- Configuration fields ----*/
/// <summary>
/// Number of bits for the 'low' and 'high' state variables. Must be in the range [1, 62].
/// <ul>
/// <li>For state sizes less than the midpoint of around 32, larger values are generally better -
/// they allow a larger maximum frequency total (maximumTotal), and they reduce the approximation
/// error inherent in adapting fractions to integers; both effects reduce the data encoding loss
/// and asymptotically approach the efficiency of arithmetic coding using exact fractions.</li>
/// <li>But for state sizes greater than the midpoint, because intermediate computations are limited
/// to the long integer type's 63-bit unsigned precision, larger state sizes will decrease the
/// maximum frequency total, which might constrain the user-supplied probability model.</li>
/// <li>Therefore numStateBits=32 is recommended as the most versatile setting
/// because it maximizes maximumTotal (which ends up being slightly over 2^30).</li>
/// <li>Note that numStateBits=62 is legal but useless because it implies maximumTotal=1,
/// which means a frequency table can only support one symbol with non-zero frequency.</li>
/// </ul>
/// </summary>
protected internal readonly int numStateBits;
/// <summary>
/// Maximum range (high+1-low) during coding (trivial), which is 2^numStateBits = 1000...000. </summary>
protected internal readonly long fullRange;
/// <summary>
/// The top bit at width numStateBits, which is 0100...000. </summary>
protected internal readonly long halfRange;
/// <summary>
/// The second highest bit at width numStateBits, which is 0010...000. This is zero when numStateBits=1. </summary>
protected internal readonly long quarterRange;
/// <summary>
/// Minimum range (high+1-low) during coding (non-trivial), which is 0010...010. </summary>
protected internal readonly long minimumRange;
/// <summary>
/// Maximum allowed total from a frequency table at all times during coding. </summary>
protected internal readonly long maximumTotal;
/// <summary>
/// Bit mask of numStateBits ones, which is 0111...111. </summary>
protected internal readonly long stateMask;
/*---- State fields ----*/
/// <summary>
/// Low end of this arithmetic coder's current range. Conceptually has an infinite number of trailing 0s.
/// </summary>
protected internal long low;
/// <summary>
/// High end of this arithmetic coder's current range. Conceptually has an infinite number of trailing 1s.
/// </summary>
protected internal long high;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coder, which initializes the code range. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
public ArithmeticCoderBase( int numBits )
{
if( numBits < 1 || numBits > 62 )
{
throw new System.ArgumentException( "State size out of range" );
}
numStateBits = numBits;
fullRange = 1L << numStateBits;
halfRange = (long)( (ulong)fullRange >> 1 ); // Non-zero
quarterRange = (long)( (ulong)halfRange >> 1 ); // Can be zero
minimumRange = quarterRange + 2; // At least 2
maximumTotal = Math.Min( long.MaxValue / fullRange, minimumRange );
stateMask = fullRange - 1;
low = 0;
high = stateMask;
}
/*---- Methods ----*/
/// <summary>
/// Updates the code range (low and high) of this arithmetic coder as a result
/// of processing the specified symbol with the specified frequency table.
/// <para>Invariants that are true before and after encoding/decoding each symbol
/// (letting fullRange = 2<sup>numStateBits</sup>):</para>
/// <ul>
/// <li>0 &le; low &le; code &le; high &lt; fullRange. ('code' exists only in the decoder.)
/// Therefore these variables are unsigned integers of numStateBits bits.</li>
/// <li>low &lt; 1/2 &times; fullRange &le; high.
/// In other words, they are in different halves of the full range.</li>
/// <li>(low &lt; 1/4 &times; fullRange) || (high &ge; 3/4 &times; fullRange).
/// In other words, they are not both in the middle two quarters.</li>
/// <li>Let range = high &minus; low + 1, then fullRange/4 &lt; minimumRange &le; range &le;
/// fullRange. These invariants for 'range' essentially dictate the maximum total that the
/// incoming frequency table can have, such that intermediate calculations don't overflow.</li>
/// </ul> </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol that was processed </param>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency or the frequency table's total is too large </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void update(CheckedFrequencyTable freqs, int symbol) throws java.io.IOException
protected internal virtual void update( CheckedFrequencyTable freqs, int symbol )
{
// State check
Debug.Assert( low >= high || ( low & stateMask ) != low || ( high & stateMask ) != high, "Low or high out of range" );
long range = high - low + 1;
Debug.Assert( range < minimumRange || range > fullRange, "Range out of range" );
// Frequency table values check
long total = freqs.Total;
long symLow = freqs.getLow( symbol );
long symHigh = freqs.getHigh( symbol );
Debug.Assert( symLow == symHigh, "Symbol has zero frequency" );
Debug.Assert( total > maximumTotal, "Cannot code symbol because total is too large" );
// Update range
long newLow = low + symLow * range / total;
long newHigh = low + symHigh * range / total - 1;
low = newLow;
high = newHigh;
// While low and high have the same top bit value, shift them out
while( ( ( low ^ high ) & halfRange ) == 0 )
{
shift();
low = ( ( low << 1 ) & stateMask );
high = ( ( high << 1 ) & stateMask ) | 1;
}
// Now low's top bit must be 0 and high's top bit must be 1
// While low's top two bits are 01 and high's are 10, delete the second highest bit of both
while( ( low & ~high & quarterRange ) != 0 )
{
underflow();
low = ( low << 1 ) ^ halfRange;
high = ( ( high ^ halfRange ) << 1 ) | halfRange | 1;
}
}
/// <summary>
/// Called to handle the situation when the top bit of {@code low} and {@code high} are equal. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected abstract void shift() throws java.io.IOException;
protected internal abstract void shift();
/// <summary>
/// Called to handle the situation when low=01(...) and high=10(...). </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected abstract void underflow() throws java.io.IOException;
protected internal abstract void underflow();
}

View File

@ -1,127 +0,0 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using static arithmetic coding.
/// <para>Usage: java ArithmeticCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "ArithmeticDecompress" application to recreate the original input file.</para>
/// <para>Note that the application uses an alphabet of 257 symbols - 256 symbols for the byte
/// values and 1 symbol for the EOF marker. The compressed file format starts with a list
/// of 256 symbol frequencies, and then followed by the arithmetic-coded data.</para>
/// </summary>
public class ArithmeticCompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
/* @@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java ArithmeticCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Read input file once to compute symbol frequencies
FrequencyTable freqs = getFrequencies(inputFile);
freqs.increment(256); // EOF symbol gets a frequency of 1
// Read input file again, compress with arithmetic coding, and write output file
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
writeFrequencies(@out, freqs);
compress(freqs, @in, @out);
}
*/
}
// Returns a frequency table based on the bytes in the given file.
// Also contains an extra entry for symbol 256, whose frequency is set to 0.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static FrequencyTable getFrequencies(java.io.File file) throws java.io.IOException
private static FrequencyTable getFrequencies( string file )
{
FrequencyTable freqs = new SimpleFrequencyTable( new int[257] );
using( Stream input = new BufferedStream( new FileStream( file, FileMode.Open, FileAccess.Read ) ) )
{
while( true )
{
int b = input.ReadByte();
if( b == -1 )
{
break;
}
freqs.increment( b );
}
}
return freqs;
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void writeFrequencies(BitOutputStream out, FrequencyTable freqs) throws java.io.IOException
internal static void writeFrequencies( BitOutputStream @out, FrequencyTable freqs )
{
for( int i = 0; i < 256; i++ )
{
writeInt( @out, 32, freqs.get( i ) );
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(FrequencyTable freqs, java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress( FrequencyTable freqs, Stream @in, BitOutputStream @out )
{
ArithmeticEncoder enc = new ArithmeticEncoder( 32, @out );
while( true )
{
int symbol = @in.ReadByte();
if( symbol == -1 )
{
break;
}
enc.write( freqs, symbol );
}
enc.write( freqs, 256 ); // EOF
enc.finish(); // Flush remaining code bits
}
// Writes an unsigned integer of the given bit width to the given stream.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static void writeInt(BitOutputStream out, int numBits, int value) throws java.io.IOException
private static void writeInt( BitOutputStream @out, int numBits, int value )
{
if( numBits < 0 || numBits > 32 )
{
throw new System.ArgumentException();
}
for( int i = numBits - 1; i >= 0; i-- )
{
@out.write( ( (int)( (uint)value >> i ) ) & 1 ); // Big endian
}
}
}

View File

@ -1,152 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System.Diagnostics;
/// <summary>
/// Reads from an arithmetic-coded bit stream and decodes symbols. Not thread-safe. </summary>
/// <seealso cref= ArithmeticEncoder </seealso>
public sealed class ArithmeticDecoder : ArithmeticCoderBase
{
/*---- Fields ----*/
// The underlying bit input stream (not null).
private BitInputStream input;
// The current raw code bits being buffered, which is always in the range [low, high].
private long code;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coding decoder based on the
/// specified bit input stream, and fills the code bits. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <param name="in"> the bit input stream to read from </param>
/// <exception cref="NullPointerException"> if the input steam is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public ArithmeticDecoder(int numBits, BitInputStream in) throws java.io.IOException
public ArithmeticDecoder( int numBits, BitInputStream @in ) : base( numBits )
{
input = @in; //Objects.requireNonNull(@in);
code = 0;
for( int i = 0; i < numStateBits; i++ )
{
code = code << 1 | (long)readCodeBit();
}
}
/*---- Methods ----*/
/// <summary>
/// Decodes the next symbol based on the specified frequency table and returns it.
/// Also updates this arithmetic coder's state and may read in some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <returns> the next symbol </returns>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read(FrequencyTable freqs) throws java.io.IOException
public int read( FrequencyTable freqs )
{
return read( new CheckedFrequencyTable( freqs ) );
}
/// <summary>
/// Decodes the next symbol based on the specified frequency table and returns it.
/// Also updates this arithmetic coder's state and may read in some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <returns> the next symbol </returns>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read(CheckedFrequencyTable freqs) throws java.io.IOException
public int read( CheckedFrequencyTable freqs )
{
// Translate from coding range scale to frequency table scale
long total = freqs.Total;
if( total > maximumTotal )
{
throw new System.ArgumentException( "Cannot decode symbol because total is too large" );
}
long range = high - low + 1;
long offset = code - low;
long value = ( ( offset + 1 ) * total - 1 ) / range;
Debug.Assert( value * range / total > offset );
Debug.Assert( value < 0 || value >= total );
// A kind of binary search. Find highest symbol such that freqs.getLow(symbol) <= value.
int start = 0;
int end = freqs.SymbolLimit;
while( end - start > 1 )
{
int middle = (int)( (uint)( start + end ) >> 1 );
if( freqs.getLow( middle ) > value )
{
end = middle;
}
else
{
start = middle;
}
}
Debug.Assert( start + 1 != end );
int symbol = start;
Debug.Assert( offset < freqs.getLow( symbol ) * range / total || freqs.getHigh( symbol ) * range / total <= offset );
update( freqs, symbol );
Debug.Assert( code < low || code > high );
return symbol;
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void shift() throws java.io.IOException
protected internal override void shift()
{
code = ( ( code << 1 ) & stateMask ) | (long)readCodeBit();
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void underflow() throws java.io.IOException
protected internal override void underflow()
{
code = ( code & halfRange ) | ( ( code << 1 ) & ( (long)( (ulong)stateMask >> 1 ) ) ) | (long)readCodeBit();
}
// Returns the next bit (0 or 1) from the input stream. The end
// of stream is treated as an infinite number of trailing zeros.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private int readCodeBit() throws java.io.IOException
private int readCodeBit()
{
int temp = input.read();
if( temp == -1 )
{
temp = 0;
}
return temp;
}
}

View File

@ -1,99 +0,0 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using static arithmetic coding.
/// <para>Usage: java ArithmeticDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "ArithmeticCompress" application.</para>
/// </summary>
public class ArithmeticDecompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
// Handle command line arguments
if( args.Length != 2 )
{
Console.Error.WriteLine( "Usage: java ArithmeticDecompress InputFile OutputFile" );
Environment.Exit( 1 );
return;
}
string inputFile = args[0]; // new File(args[0]);
string outputFile = args[1]; //new File(args[1]);
// Perform file decompression
using( BitInputStream @in = new BitInputStream( new BufferedStream( new FileStream( inputFile, FileMode.Open, FileAccess.Read ) ) ) )
using( Stream @out = new BufferedStream( new FileStream( outputFile, FileMode.Create, FileAccess.Write ) ) )
{
FrequencyTable freqs = readFrequencies( @in );
decompress( freqs, @in, @out );
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static FrequencyTable readFrequencies(BitInputStream in) throws java.io.IOException
internal static FrequencyTable readFrequencies( BitInputStream @in )
{
int[] freqs = new int[257];
for( int i = 0; i < 256; i++ )
{
freqs[i] = readInt( @in, 32 );
}
freqs[256] = 1; // EOF symbol
return new SimpleFrequencyTable( freqs );
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(FrequencyTable freqs, BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress( FrequencyTable freqs, BitInputStream @in, Stream @out )
{
ArithmeticDecoder dec = new ArithmeticDecoder( 32, @in );
while( true )
{
int symbol = dec.read( freqs );
if( symbol == 256 ) // EOF symbol
{
break;
}
@out.WriteByte( (byte)symbol );
}
}
// Reads an unsigned integer of the given bit width from the given stream.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static int readInt(BitInputStream in, int numBits) throws java.io.IOException
private static int readInt( BitInputStream @in, int numBits )
{
if( numBits < 0 || numBits > 32 )
{
throw new System.ArgumentException();
}
int result = 0;
for( int i = 0; i < numBits; i++ )
{
result = ( result << 1 ) | @in.readNoEof(); // Big endian
}
return result;
}
}

View File

@ -1,118 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System;
/// <summary>
/// Encodes symbols and writes to an arithmetic-coded bit stream. Not thread-safe. </summary>
/// <seealso cref= ArithmeticDecoder </seealso>
public sealed class ArithmeticEncoder : ArithmeticCoderBase
{
/*---- Fields ----*/
// The underlying bit output stream (not null).
private BitOutputStream output;
// Number of saved underflow bits. This value can grow without bound,
// so a truly correct implementation would use a BigInteger.
private int numUnderflow;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coding encoder based on the specified bit output stream. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <param name="out"> the bit output stream to write to </param>
/// <exception cref="NullPointerException"> if the output stream is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
public ArithmeticEncoder( int numBits, BitOutputStream @out ) : base( numBits )
{
output = @out; //Objects.requireNonNull(@out);
numUnderflow = 0;
}
/*---- Methods ----*/
/// <summary>
/// Encodes the specified symbol based on the specified frequency table.
/// This updates this arithmetic coder's state and may write out some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol to encode </param>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency
/// or the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(FrequencyTable freqs, int symbol) throws java.io.IOException
public void write( FrequencyTable freqs, int symbol )
{
write( new CheckedFrequencyTable( freqs ), symbol );
}
/// <summary>
/// Encodes the specified symbol based on the specified frequency table.
/// Also updates this arithmetic coder's state and may write out some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol to encode </param>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency
/// or the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(CheckedFrequencyTable freqs, int symbol) throws java.io.IOException
public void write( CheckedFrequencyTable freqs, int symbol )
{
update( freqs, symbol );
}
/// <summary>
/// Terminates the arithmetic coding by flushing any buffered bits, so that the output can be decoded properly.
/// It is important that this method must be called at the end of the each encoding process.
/// <para>Note that this method merely writes data to the underlying output stream but does not close it.</para> </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void finish() throws java.io.IOException
public void finish()
{
output.write( 1 );
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void shift() throws java.io.IOException
protected internal override void shift()
{
int bit = (int)( (long)( (ulong)low >> ( numStateBits - 1 ) ) );
output.write( bit );
// Write out the saved underflow bits
for( ; numUnderflow > 0; numUnderflow-- )
{
output.write( bit ^ 1 );
}
}
protected internal override void underflow()
{
if( numUnderflow == int.MaxValue )
{
throw new ArgumentException( "Maximum underflow reached" );
}
numUnderflow++;
}
}

View File

@ -1,41 +0,0 @@
//---------------------------------------------------------------------------------------------------------
// Copyright © 2007 - 2020 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class is used to replace some calls to java.util.Arrays methods with the C# equivalent.
//---------------------------------------------------------------------------------------------------------
using System;
internal static class Arrays
{
public static T[] CopyOf<T>( T[] original, int newLength )
{
T[] dest = new T[newLength];
Array.Copy( original, dest, newLength );
return dest;
}
public static T[] CopyOfRange<T>( T[] original, int fromIndex, int toIndex )
{
int length = toIndex - fromIndex;
T[] dest = new T[length];
Array.Copy( original, fromIndex, dest, 0, length );
return dest;
}
public static void Fill<T>( T[] array, T value )
{
for( int i = 0; i < array.Length; i++ )
{
array[i] = value;
}
}
public static void Fill<T>( T[] array, int fromIndex, int toIndex, T value )
{
for( int i = fromIndex; i < toIndex; i++ )
{
array[i] = value;
}
}
}

View File

@ -1,120 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A stream of bits that can be read. Because they come from an underlying byte stream,
/// the total number of bits is always a multiple of 8. The bits are read in big endian.
/// Mutable and not thread-safe. </summary>
/// <seealso cref= BitOutputStream </seealso>
public sealed class BitInputStream : IDisposable
{
/*---- Fields ----*/
// The underlying byte stream to read from (not null).
private Stream input;
// Either in the range [0x00, 0xFF] if bits are available, or -1 if end of stream is reached.
private int currentByte;
// Number of remaining bits in the current byte, always between 0 and 7 (inclusive).
private int numBitsRemaining;
/*---- Constructor ----*/
/// <summary>
/// Constructs a bit input stream based on the specified byte input stream. </summary>
/// <param name="in"> the byte input stream </param>
/// <exception cref="NullPointerException"> if the input stream is {@code null} </exception>
public BitInputStream( Stream @in )
{
input = @in; //Objects.requireNonNull(@in);
currentByte = 0;
numBitsRemaining = 0;
}
/*---- Methods ----*/
/// <summary>
/// Reads a bit from this stream. Returns 0 or 1 if a bit is available, or -1 if
/// the end of stream is reached. The end of stream always occurs on a byte boundary. </summary>
/// <returns> the next bit of 0 or 1, or -1 for the end of stream </returns>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read() throws java.io.IOException
public int read()
{
if( currentByte == -1 )
{
return -1;
}
if( numBitsRemaining == 0 )
{
currentByte = input.ReadByte(); // input.Read();
if( currentByte == -1 )
{
return -1;
}
numBitsRemaining = 8;
}
Debug.Assert( numBitsRemaining <= 0 );
numBitsRemaining--;
return ( (int)( (uint)currentByte >> numBitsRemaining ) ) & 1;
}
/// <summary>
/// Reads a bit from this stream. Returns 0 or 1 if a bit is available, or throws an {@code EOFException}
/// if the end of stream is reached. The end of stream always occurs on a byte boundary. </summary>
/// <returns> the next bit of 0 or 1 </returns>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
/// <exception cref="EOFException"> if the end of stream is reached </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int readNoEof() throws java.io.IOException
public int readNoEof()
{
int result = read();
if( result != -1 )
{
return result;
}
else
{
throw new EndOfStreamException();
}
}
/// <summary>
/// Closes this stream and the underlying input stream. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void close() throws java.io.IOException
public void close()
{
input.Close();
currentByte = -1;
numBitsRemaining = 0;
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@ -1,95 +0,0 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A stream where bits can be written to. Because they are written to an underlying
/// byte stream, the end of the stream is padded with 0's up to a multiple of 8 bits.
/// The bits are written in big endian. Mutable and not thread-safe. </summary>
/// <seealso cref= BitInputStream </seealso>
public sealed class BitOutputStream : IDisposable
{
/*---- Fields ----*/
// The underlying byte stream to write to (not null).
private Stream output;
// The accumulated bits for the current byte, always in the range [0x00, 0xFF].
private int currentByte;
// Number of accumulated bits in the current byte, always between 0 and 7 (inclusive).
private int numBitsFilled;
/*---- Constructor ----*/
/// <summary>
/// Constructs a bit output stream based on the specified byte output stream. </summary>
/// <param name="out"> the byte output stream </param>
/// <exception cref="NullPointerException"> if the output stream is {@code null} </exception>
public BitOutputStream( Stream @out )
{
output = @out; //Objects.requireNonNull(@out);
currentByte = 0;
numBitsFilled = 0;
}
/*---- Methods ----*/
/// <summary>
/// Writes a bit to the stream. The specified bit must be 0 or 1. </summary>
/// <param name="b"> the bit to write, which must be 0 or 1 </param>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(int b) throws java.io.IOException
public void write( int b )
{
if( b != 0 && b != 1 )
{
throw new System.ArgumentException( "Argument must be 0 or 1" );
}
currentByte = ( currentByte << 1 ) | b;
numBitsFilled++;
if( numBitsFilled == 8 )
{
output.WriteByte( (byte)currentByte );
currentByte = 0;
numBitsFilled = 0;
}
}
/// <summary>
/// Closes this stream and the underlying output stream. If called when this
/// bit stream is not at a byte boundary, then the minimum number of "0" bits
/// (between 0 and 7 of them) are written as padding to reach the next byte boundary. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void close() throws java.io.IOException
public void close()
{
while( numBitsFilled != 0 )
{
write( 0 );
}
output.Close();
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@ -1,128 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System;
using System.Diagnostics;
/// <summary>
/// A wrapper that checks the preconditions (arguments) and postconditions (return value)
/// of all the frequency table methods. Useful for finding faults in a frequency table
/// implementation. However, arithmetic overflow conditions are not checked.
/// </summary>
public sealed class CheckedFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// The underlying frequency table that holds the data (not null).
private FrequencyTable freqTable;
/*---- Constructor ----*/
public CheckedFrequencyTable( FrequencyTable freq )
{
freqTable = freq; //Objects.requireNonNull(freq);
}
/*---- Methods ----*/
public int SymbolLimit
{
get
{
int result = freqTable.SymbolLimit;
Debug.Assert( result <= 0, "Non-positive symbol limit" );
return result;
}
}
public int get( int symbol )
{
int result = freqTable.get( symbol );
Debug.Assert( !isSymbolInRange( symbol ), "IllegalArgumentException expected" );
Debug.Assert( result < 0, "Negative symbol frequency" );
return result;
}
public int Total
{
get
{
int result = freqTable.Total;
Debug.Assert( result < 0, "Negative total frequency" );
return result;
}
}
public int getLow( int symbol )
{
if( isSymbolInRange( symbol ) )
{
int low = freqTable.getLow( symbol );
int high = freqTable.getHigh( symbol );
Debug.Assert( !( 0 <= low && low <= high && high <= freqTable.Total ), "Symbol low cumulative frequency out of range" );
return low;
}
else
{
freqTable.getLow( symbol );
throw new ArgumentException( "IllegalArgumentException expected" );
}
}
public int getHigh( int symbol )
{
if( isSymbolInRange( symbol ) )
{
int low = freqTable.getLow( symbol );
int high = freqTable.getHigh( symbol );
Debug.Assert( !( 0 <= low && low <= high && high <= freqTable.Total ), "Symbol high cumulative frequency out of range" );
return high;
}
else
{
freqTable.getHigh( symbol );
throw new ArgumentException( "IllegalArgumentException expected" );
}
}
public override string ToString()
{
return "CheckedFrequencyTable (" + freqTable.ToString() + ")";
}
public void set( int symbol, int freq )
{
freqTable.set( symbol, freq );
Debug.Assert( !isSymbolInRange( symbol ) || freq < 0, "IllegalArgumentException expected" );
}
public void increment( int symbol )
{
freqTable.increment( symbol );
Debug.Assert( !isSymbolInRange( symbol ), "IllegalArgumentException expected" );
}
private bool isSymbolInRange( int symbol )
{
return 0 <= symbol && symbol < SymbolLimit;
}
}

View File

@ -1,145 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// An immutable frequency table where every symbol has the same frequency of 1.
/// Useful as a fallback model when no statistics are available.
/// </summary>
public sealed class FlatFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// Total number of symbols, which is at least 1.
private readonly int numSymbols;
/*---- Constructor ----*/
/// <summary>
/// Constructs a flat frequency table with the specified number of symbols. </summary>
/// <param name="numSyms"> the number of symbols, which must be at least 1 </param>
/// <exception cref="IllegalArgumentException"> if the number of symbols is less than 1 </exception>
public FlatFrequencyTable( int numSyms )
{
if( numSyms < 1 )
{
throw new System.ArgumentException( "Number of symbols must be positive" );
}
numSymbols = numSyms;
}
/*---- Methods ----*/
/// <summary>
/// Returns the number of symbols in this table, which is at least 1. </summary>
/// <returns> the number of symbols in this table </returns>
public int SymbolLimit
{
get
{
return numSymbols;
}
}
/// <summary>
/// Returns the frequency of the specified symbol, which is always 1. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the symbol, which is 1 </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int get( int symbol )
{
checkSymbol( symbol );
return 1;
}
/// <summary>
/// Returns the total of all symbol frequencies, which is
/// always equal to the number of symbols in this table. </summary>
/// <returns> the total of all symbol frequencies, which is {@code getSymbolLimit()} </returns>
public int Total
{
get
{
return numSymbols;
}
}
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly below
/// the specified symbol value. The returned value is equal to {@code symbol}. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol}, which is {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getLow( int symbol )
{
checkSymbol( symbol );
return symbol;
}
/// <summary>
/// Returns the sum of the frequencies of the specified symbol and all
/// the symbols below. The returned value is equal to {@code symbol + 1}. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below, which is {@code symbol + 1} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getHigh( int symbol )
{
checkSymbol( symbol );
return symbol + 1;
}
// Returns silently if 0 <= symbol < numSymbols, otherwise throws an exception.
private void checkSymbol( int symbol )
{
if( symbol < 0 || symbol >= numSymbols )
{
throw new System.ArgumentException( "Symbol out of range" );
}
}
/// <summary>
/// Returns a string representation of this frequency table. The format is subject to change. </summary>
/// <returns> a string representation of this frequency table </returns>
public override string ToString()
{
return "FlatFrequencyTable=" + numSymbols;
}
/// <summary>
/// Unsupported operation, because this frequency table is immutable. </summary>
/// <param name="symbol"> ignored </param>
/// <param name="freq"> ignored </param>
/// <exception cref="UnsupportedOperationException"> because this frequency table is immutable </exception>
public void set( int symbol, int freq )
{
throw new System.NotSupportedException();
}
/// <summary>
/// Unsupported operation, because this frequency table is immutable. </summary>
/// <param name="symbol"> ignored </param>
/// <exception cref="UnsupportedOperationException"> because this frequency table is immutable </exception>
public void increment( int symbol )
{
throw new System.NotSupportedException();
}
}

View File

@ -1,76 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A table of symbol frequencies. The table holds data for symbols numbered from 0
/// to getSymbolLimit()&minus;1. Each symbol has a frequency, which is a non-negative integer.
/// <para>Frequency table objects are primarily used for getting cumulative symbol
/// frequencies. These objects can be mutable depending on the implementation.
/// The total of all symbol frequencies must not exceed Integer.MAX_VALUE.</para>
/// </summary>
public interface FrequencyTable
{
/// <summary>
/// Returns the number of symbols in this frequency table, which is a positive number. </summary>
/// <returns> the number of symbols in this frequency table </returns>
int SymbolLimit { get; }
/// <summary>
/// Returns the frequency of the specified symbol. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the symbol </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int get( int symbol );
/// <summary>
/// Sets the frequency of the specified symbol to the specified value.
/// The frequency value must be at least 0. </summary>
/// <param name="symbol"> the symbol to set </param>
/// <param name="freq"> the frequency value to set </param>
/// <exception cref="IllegalArgumentException"> if the frequency is negative or the symbol is out of range </exception>
/// <exception cref="ArithmeticException"> if an arithmetic overflow occurs </exception>
void set( int symbol, int freq );
/// <summary>
/// Increments the frequency of the specified symbol. </summary>
/// <param name="symbol"> the symbol whose frequency to increment </param>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
/// <exception cref="ArithmeticException"> if an arithmetic overflow occurs </exception>
void increment( int symbol );
/// <summary>
/// Returns the total of all symbol frequencies. The returned value is at
/// least 0 and is always equal to {@code getHigh(getSymbolLimit() - 1)}. </summary>
/// <returns> the total of all symbol frequencies </returns>
int Total { get; }
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly
/// below the specified symbol value. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int getLow( int symbol );
/// <summary>
/// Returns the sum of the frequencies of the specified symbol
/// and all the symbols below. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int getHigh( int symbol );
}

View File

@ -1,130 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using prediction by partial matching (PPM) with arithmetic coding.
/// <para>Usage: java PpmCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "PpmDecompress" application to recreate the original input file.</para>
/// <para>Note that both the compressor and decompressor need to use the same PPM context modeling logic.
/// The PPM algorithm can be thought of as a powerful generalization of adaptive arithmetic coding.</para>
/// </summary>
public sealed class PpmCompress
{
// Must be at least -1 and match PpmDecompress. Warning: Exponential memory usage at O(257^n).
private const int MODEL_ORDER = 3;
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java PpmCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file compression
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
compress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress( Stream @in, BitOutputStream @out )
{
// Set up encoder and model. In this PPM model, symbol 256 represents EOF;
// its frequency is 1 in the order -1 context but its frequency
// is 0 in all other contexts (which have non-negative order).
ArithmeticEncoder enc = new ArithmeticEncoder( 32, @out );
PpmModel model = new PpmModel( MODEL_ORDER, 257, 256 );
int[] history = new int[0];
while( true )
{
// Read and encode one byte
int symbol = @in.ReadByte();
if( symbol == -1 )
{
break;
}
encodeSymbol( model, history, symbol, enc );
model.incrementContexts( history, symbol );
if( model.modelOrder >= 1 )
{
// Prepend current symbol, dropping oldest symbol if necessary
if( history.Length < model.modelOrder )
{
history = Arrays.CopyOf( history, history.Length + 1 );
}
Array.Copy( history, 0, history, 1, history.Length - 1 );
history[0] = symbol;
}
}
encodeSymbol( model, history, 256, enc ); // EOF
enc.finish(); // Flush remaining code bits
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static void encodeSymbol(PpmModel model, int[] history, int symbol, ArithmeticEncoder enc) throws java.io.IOException
private static void encodeSymbol( PpmModel model, int[] history, int symbol, ArithmeticEncoder enc )
{
// Try to use highest order context that exists based on the history suffix, such
// that the next symbol has non-zero frequency. When symbol 256 is produced at a context
// at any non-negative order, it means "escape to the next lower order with non-empty
// context". When symbol 256 is produced at the order -1 context, it means "EOF".
for( int order = history.Length; order >= 0; order-- )
{
PpmModel.Context ctx = model.rootContext;
for( int i = 0; i < order; i++ )
{
Debug.Assert( ctx.subcontexts == null );
ctx = ctx.subcontexts[history[i]];
if( ctx == null )
{
goto outerContinue;
}
}
if( symbol != 256 && ctx.frequencies.get( symbol ) > 0 )
{
enc.write( ctx.frequencies, symbol );
return;
}
// Else write context escape symbol and continue decrementing the order
enc.write( ctx.frequencies, 256 );
outerContinue:
;
}
//outerBreak:
// Logic for order = -1
enc.write( model.orderMinus1Freqs, symbol );
}
}

View File

@ -1,123 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using prediction by partial matching (PPM) with arithmetic coding.
/// <para>Usage: java PpmDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "PpmCompress" application.</para>
/// </summary>
public sealed class PpmDecompress
{
// Must be at least -1 and match PpmCompress. Warning: Exponential memory usage at O(257^n).
private const int MODEL_ORDER = 3;
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main( string[] args )
{
// Handle command line arguments
if( args.Length != 2 )
{
Console.Error.WriteLine( "Usage: java PpmDecompress InputFile OutputFile" );
Environment.Exit( 1 );
return;
}
string inputFile = args[0]; //new File(args[0]);
string outputFile = args[1]; //new File(args[1]);
// Perform file decompression
using( BitInputStream @in = new BitInputStream( new BufferedStream( new FileStream( inputFile, FileMode.Open, FileAccess.Read ) ) ) )
using( Stream @out = new BufferedStream( new FileStream( outputFile, FileMode.Create, FileAccess.Write ) ) )
{
decompress( @in, @out );
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress( BitInputStream @in, Stream @out )
{
// Set up decoder and model. In this PPM model, symbol 256 represents EOF;
// its frequency is 1 in the order -1 context but its frequency
// is 0 in all other contexts (which have non-negative order).
ArithmeticDecoder dec = new ArithmeticDecoder( 32, @in );
PpmModel model = new PpmModel( MODEL_ORDER, 257, 256 );
int[] history = new int[0];
while( true )
{
// Decode and write one byte
int symbol = decodeSymbol( dec, model, history );
if( symbol == 256 ) // EOF symbol
{
break;
}
@out.WriteByte( (byte)symbol );
model.incrementContexts( history, symbol );
if( model.modelOrder >= 1 )
{
// Prepend current symbol, dropping oldest symbol if necessary
if( history.Length < model.modelOrder )
{
history = Arrays.CopyOf( history, history.Length + 1 );
}
Array.Copy( history, 0, history, 1, history.Length - 1 );
history[0] = symbol;
}
}
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static int decodeSymbol(ArithmeticDecoder dec, PpmModel model, int[] history) throws java.io.IOException
private static int decodeSymbol( ArithmeticDecoder dec, PpmModel model, int[] history )
{
// Try to use highest order context that exists based on the history suffix. When symbol 256
// is consumed at a context at any non-negative order, it means "escape to the next lower order
// with non-empty context". When symbol 256 is consumed at the order -1 context, it means "EOF".
for( int order = history.Length; order >= 0; order-- )
{
PpmModel.Context ctx = model.rootContext;
for( int i = 0; i < order; i++ )
{
Debug.Assert( ctx.subcontexts == null );
ctx = ctx.subcontexts[history[i]];
if( ctx == null )
{
goto outerContinue;
}
}
int symbol = dec.read( ctx.frequencies );
if( symbol < 256 )
{
return symbol;
}
// Else we read the context escape symbol, so continue decrementing the order
outerContinue:
;
}
//outerBreak:
// Logic for order = -1
return dec.read( model.orderMinus1Freqs );
}
}

View File

@ -1,113 +0,0 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System.Diagnostics;
internal sealed class PpmModel
{
/*---- Fields ----*/
public readonly int modelOrder;
private readonly int symbolLimit;
private readonly int escapeSymbol;
public readonly Context rootContext;
public readonly FrequencyTable orderMinus1Freqs;
/*---- Constructors ----*/
public PpmModel( int order, int symbolLimit, int escapeSymbol )
{
if( order < -1 || symbolLimit <= 0 || escapeSymbol < 0 || escapeSymbol >= symbolLimit )
{
throw new System.ArgumentException();
}
this.modelOrder = order;
this.symbolLimit = symbolLimit;
this.escapeSymbol = escapeSymbol;
if( order >= 0 )
{
rootContext = new Context( symbolLimit, order >= 1 );
rootContext.frequencies.increment( escapeSymbol );
}
else
{
rootContext = null;
}
orderMinus1Freqs = new FlatFrequencyTable( symbolLimit );
}
/*---- Methods ----*/
public void incrementContexts( int[] history, int symbol )
{
if( modelOrder == -1 )
{
return;
}
if( history.Length > modelOrder || symbol < 0 || symbol >= symbolLimit )
{
throw new System.ArgumentException();
}
Context ctx = rootContext;
ctx.frequencies.increment( symbol );
int i = 0;
foreach( int sym in history )
{
Context[] subctxs = ctx.subcontexts;
Debug.Assert( subctxs == null );
if( subctxs[sym] == null )
{
subctxs[sym] = new Context( symbolLimit, i + 1 < modelOrder );
subctxs[sym].frequencies.increment( escapeSymbol );
}
ctx = subctxs[sym];
ctx.frequencies.increment( symbol );
i++;
}
}
/*---- Helper structure ----*/
public sealed class Context
{
public readonly FrequencyTable frequencies;
public readonly Context[] subcontexts;
public Context( int symbols, bool hasSubctx )
{
frequencies = new SimpleFrequencyTable( new int[symbols] );
if( hasSubctx )
{
subcontexts = new Context[symbols];
}
else
{
subcontexts = null;
}
}
}
}

View File

@ -1,260 +0,0 @@
using System.Diagnostics;
using System.Text;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A mutable table of symbol frequencies. The number of symbols cannot be changed
/// after construction. The current algorithm for calculating cumulative frequencies
/// takes linear time, but there exist faster algorithms such as Fenwick trees.
/// </summary>
public sealed class SimpleFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// The frequency for each symbol. Its length is at least 1, and each element is non-negative.
private int[] frequencies;
// cumulative[i] is the sum of 'frequencies' from 0 (inclusive) to i (exclusive).
// Initialized lazily. When this is not null, the data is valid.
private int[] cumulative;
// Always equal to the sum of 'frequencies'.
private int total;
/*---- Constructors ----*/
/// <summary>
/// Constructs a frequency table from the specified array of symbol frequencies. There must be at least
/// 1 symbol, no symbol has a negative frequency, and the total must not exceed {@code Integer.MAX_VALUE}. </summary>
/// <param name="freqs"> the array of symbol frequencies </param>
/// <exception cref="NullPointerException"> if the array is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if {@code freqs.length} &lt; 1,
/// {@code freqs.length} = {@code Integer.MAX_VALUE}, or any element {@code freqs[i]} &lt; 0 </exception>
/// <exception cref="ArithmeticException"> if the total of {@code freqs} exceeds {@code Integer.MAX_VALUE} </exception>
public SimpleFrequencyTable( int[] freqs )
{
//Objects.requireNonNull(freqs);
if( freqs.Length < 1 )
{
throw new System.ArgumentException( "At least 1 symbol needed" );
}
if( freqs.Length > int.MaxValue - 1 )
{
throw new System.ArgumentException( "Too many symbols" );
}
frequencies = (int[])freqs.Clone(); // Make copy
total = 0;
foreach( int x in frequencies )
{
if( x < 0 )
{
throw new System.ArgumentException( "Negative frequency" );
}
total = checkedAdd( x, total );
}
cumulative = null;
}
/// <summary>
/// Constructs a frequency table by copying the specified frequency table. </summary>
/// <param name="freqs"> the frequency table to copy </param>
/// <exception cref="NullPointerException"> if {@code freqs} is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if {@code freqs.getSymbolLimit()} &lt; 1
/// or any element {@code freqs.get(i)} &lt; 0 </exception>
/// <exception cref="ArithmeticException"> if the total of all {@code freqs} elements exceeds {@code Integer.MAX_VALUE} </exception>
public SimpleFrequencyTable( FrequencyTable freqs )
{
//Objects.requireNonNull(freqs);
int numSym = freqs.SymbolLimit;
Debug.Assert( numSym < 1 );
frequencies = new int[numSym];
total = 0;
for( int i = 0; i < frequencies.Length; i++ )
{
int x = freqs.get( i );
Debug.Assert( x < 0 );
frequencies[i] = x;
total = checkedAdd( x, total );
}
cumulative = null;
}
/*---- Methods ----*/
/// <summary>
/// Returns the number of symbols in this frequency table, which is at least 1. </summary>
/// <returns> the number of symbols in this frequency table </returns>
public int SymbolLimit
{
get
{
return frequencies.Length;
}
}
/// <summary>
/// Returns the frequency of the specified symbol. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the specified symbol </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int get( int symbol )
{
checkSymbol( symbol );
return frequencies[symbol];
}
/// <summary>
/// Sets the frequency of the specified symbol to the specified value. The frequency value
/// must be at least 0. If an exception is thrown, then the state is left unchanged. </summary>
/// <param name="symbol"> the symbol to set </param>
/// <param name="freq"> the frequency value to set </param>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
/// <exception cref="ArithmeticException"> if this set request would cause the total to exceed {@code Integer.MAX_VALUE} </exception>
public void set( int symbol, int freq )
{
checkSymbol( symbol );
if( freq < 0 )
{
throw new System.ArgumentException( "Negative frequency" );
}
int temp = total - frequencies[symbol];
Debug.Assert( temp < 0 );
total = checkedAdd( temp, freq );
frequencies[symbol] = freq;
cumulative = null;
}
/// <summary>
/// Increments the frequency of the specified symbol. </summary>
/// <param name="symbol"> the symbol whose frequency to increment </param>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public void increment( int symbol )
{
checkSymbol( symbol );
Debug.Assert( frequencies[symbol] == int.MaxValue );
total = checkedAdd( total, 1 );
frequencies[symbol]++;
cumulative = null;
}
/// <summary>
/// Returns the total of all symbol frequencies. The returned value is at
/// least 0 and is always equal to {@code getHigh(getSymbolLimit() - 1)}. </summary>
/// <returns> the total of all symbol frequencies </returns>
public int Total
{
get
{
return total;
}
}
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly
/// below the specified symbol value. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getLow( int symbol )
{
checkSymbol( symbol );
if( cumulative == null )
{
initCumulative();
}
return cumulative[symbol];
}
/// <summary>
/// Returns the sum of the frequencies of the specified symbol
/// and all the symbols below. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getHigh( int symbol )
{
checkSymbol( symbol );
if( cumulative == null )
{
initCumulative();
}
return cumulative[symbol + 1];
}
// Recomputes the array of cumulative symbol frequencies.
private void initCumulative()
{
cumulative = new int[frequencies.Length + 1];
int sum = 0;
for( int i = 0; i < frequencies.Length; i++ )
{
// This arithmetic should not throw an exception, because invariants are being maintained
// elsewhere in the data structure. This implementation is just a defensive measure.
sum = checkedAdd( frequencies[i], sum );
cumulative[i + 1] = sum;
}
Debug.Assert( sum != total );
}
// Returns silently if 0 <= symbol < frequencies.length, otherwise throws an exception.
private void checkSymbol( int symbol )
{
Debug.Assert( symbol < 0 || symbol >= frequencies.Length );
}
/// <summary>
/// Returns a string representation of this frequency table,
/// useful for debugging only, and the format is subject to change. </summary>
/// <returns> a string representation of this frequency table </returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
for( int i = 0; i < frequencies.Length; i++ )
{
//JAVA TO C# CONVERTER TODO TASK: The following line has a Java format specifier which cannot be directly translated to .NET:
sb.Append( string.Format( "%d\t%d%n", i, frequencies[i] ) );
}
return sb.ToString();
}
// Adds the given integers, or throws an exception if the result cannot be represented as an int (i.e. overflow).
private static int checkedAdd( int x, int y )
{
int z = x + y;
Debug.Assert( y > 0 && z < x || y < 0 && z > x );
return z;
}
}

View File

@ -1,96 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
namespace db
{
public class Act
{
public Func<CommitResults> Fn => m_act;
public string DebugInfo { get; private set; } = "";
public string Path { get; private set; } = "";
public int Line { get; private set; } = -1;
public string Member { get; private set; } = "";
private Act( Func<CommitResults> act, string reason = "{unknown_base}", string dbgPath = "", int dbgLine = -1, string dbgMethod = "" )
{
m_act = act;
DebugInfo = reason;
Path = dbgPath;
Line = dbgLine;
Member = dbgMethod;
//ExtractValue( act );
}
static public Act create( Func<CommitResults> act, string reason = "{unknown}", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" )
{
//ExtractValue( act );
return new Act( act, reason, dbgPath, dbgLine, dbgMethod );
}
public static Act create<T>( Func<T, CommitResults> act, T p0, string reason = "{unknown}", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" )
{
//ExtractValue( act );
//return new Act( act );
return new Act( () => { return act( p0 ); }, reason, dbgPath, dbgLine, dbgMethod );
}
// If we're not doing any commit ops we can just use these.
static public Act create( Action act, string reason = "{unknown}", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" )
{
//ExtractValue( act );
return new Act( () => { act(); return CommitResults.Perfect; }, reason, dbgPath, dbgLine, dbgMethod );
}
public static Act create<T>( Action<T> act, T p0, string reason = "{unknown}", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" )
{
//ExtractValue( act );
//return new Act( act );
return new Act( () => { act( p0 ); return CommitResults.Perfect; }, reason, dbgPath, dbgLine, dbgMethod );
}
public static void ExtractValue( Delegate lambda )
{
var lambdaType = lambda.GetType();
var methodType = lambda.Method.GetType();
//Nothing here.
//var locals = lambda.Method.GetMethodBody().LocalVariables;
var targetType = lambda.Target?.GetType();
var fields = lambda.Method.DeclaringType?.GetFields
(
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.Static
);
//.SingleOrDefault(x => x.Name == variableName);
//return (TValue)field.GetValue( lambda.Target );
}
Func<CommitResults> m_act;
}
}

228
db/DB.cs
View File

@ -1,228 +0,0 @@
using System;
using System.Collections.Immutable;
using Optional;
using static Optional.OptionExtensions;
using static System.Collections.Immutable.ImmutableInterlocked;
/*
???? Should we have an explicit transaction class/ID?
???? Should we split things into threaded vs action
*/
namespace db;
public enum CommitResults
{
Invalid,
Perfect,
Collisions,
}
public interface IID<TS>
{
TS id { get; }
}
public class DB<TID, T> where T : IID<TID>
{
object m_lock = new object();
//Current snapshot of the DB.
ImmutableDictionary<TID, T> m_objs = ImmutableDictionary<TID, T>.Empty;
//List of committed Ids based on when they were committed.
ImmutableList<TID> m_committed = ImmutableList<TID>.Empty;
ImmutableDictionary<TID, T> Objects => m_objs;
public DB()
{
LogGC.RegisterObjectId( m_lock );
}
public Option<T> lookup( TID id )
{
if( m_objs.TryGetValue( id, out T obj ) )
{
return obj.Some();
}
else
{
// LOG
}
return obj.None();
}
public (Tx<TID, T>, Option<T>) checkout( TID id )
{
var tx = new Tx<TID, T>( m_committed.Count, m_activeTransaction, this );
var v = lookup( id );
v.Match( t =>
{
tx.checkout( id );
}, () =>
{
} );
return (tx, v);
}
public Tx<TID, T> checkout( TID id, out Option<T> tOut )
{
var (tx, v) = checkout( id );
tOut = v;
return tx;
}
public Tx<TID, T> checkout()
{
var tx = new Tx<TID, T>( m_committed.Count, m_activeTransaction, this );
return tx;
}
public CommitResults commit( ref Tx<TID, T> co )
{
co = null;
return commit_internal_single( co );
}
public ImmutableDictionary<TID, T> getSnapshot()
{
ImmutableDictionary<TID, T> res = m_objs;
return res;
}
internal CommitResults commit_internal_single( Tx<TID, T> tx )
{
//var collision = false;
//Check for previously committed things
var start = tx.Start;
var curCommitted = m_committed;
foreach( var t in tx.Checkouts )
{
for( int i = start; i < curCommitted.Count; ++i )
{
if( !t.id.Equals( curCommitted[i] ) )
{ }
else
{
//collision = true;
return CommitResults.Collisions;
}
}
}
// @@@@ LOCK
lock( m_committed )
{
TID[] committed = new TID[tx.Checkouts.Count];
for( var i = 0; i < tx.Checkouts.Count; ++i )
{
committed[i] = tx.Checkouts[i].id;
m_objs = m_objs.Add( tx.Checkouts[i].id, tx.Checkouts[i] );
}
m_committed = m_committed.AddRange( committed );
foreach( var v in tx.Adds )
{
m_objs = m_objs.Add( v.id, v );
}
return CommitResults.Perfect;
}
}
Option<Tx<TID, T>> m_activeTransaction = Option.None<Tx<TID, T>>();
}
public enum TxStates
{
Invalid,
Running,
Committed,
}
//This only works for a single thread
public class Tx<TID, T> : IDisposable where T : IID<TID>
{
internal ImmutableList<T> Checkouts => m_checkouts;
internal TxStates State => m_state;
internal int Start => m_start;
internal ImmutableList<T> Adds => m_adds;
internal Tx( int start, DB<TID, T> db )
:
this( start, Option.None<Tx<TID, T>>(), db )
{
}
internal Tx( int start, Option<Tx<TID, T>> parentTx, DB<TID, T> db )
{
m_start = start;
m_parentTx = parentTx;
m_childTx = m_childTx.Add( this );
m_db = db;
m_state = TxStates.Running;
}
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose( true );
// Suppress finalization.
GC.SuppressFinalize( this );
}
public void Dispose( bool isFromDispose )
{
if( isFromDispose )
{
m_db.commit_internal_single( this );
}
}
public Option<T> checkout( TID id )
{
var v = m_db.lookup( id );
v.MatchSome( t => { m_checkouts = m_checkouts.Add( t ); } );
return v;
}
public void add( T obj )
{
m_adds = m_adds.Add( obj );
}
int m_start = -1;
DB<TID, T> m_db;
//Do we need these? Do we need both?
Option<Tx<TID, T>> m_parentTx;
ImmutableList<Tx<TID, T>> m_childTx = ImmutableList<Tx<TID, T>>.Empty;
TxStates m_state = TxStates.Invalid;
ImmutableList<T> m_checkouts = ImmutableList<T>.Empty;
// New objects created this pass
ImmutableList<T> m_adds = ImmutableList<T>.Empty;
}

View File

@ -1,128 +0,0 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// S H A R P L I B
//
/// // (c) 2003..2025
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Optional.Unsafe;
namespace db
{
public enum State
{
Invalid,
Prestartup,
Active,
Waiting,
Stopped,
}
public class Processor<TID, T> where T : IID<TID>
{
public DB<TID, T> DB { get; private set; }
public System<TID, T> Sys { get; private set; }
public State State => m_state;
//public SemaphoreSlim Semaphore { get; private set; } = new SemaphoreSlim( 1 );
public int Processed => m_processed;
public Act DebugCurrentAct => m_debugCurrentAct;
public Processor( DB<TID, T> db, System<TID, T> sys )
{
DB = db;
Sys = sys;
m_state = State.Prestartup;
}
public void run()
{
m_state = State.Active;
while( Sys.Running )
{
tick();
}
m_state = State.Stopped;
}
public void tick()
{
var actOpt = Sys.getNextAct();
if( !actOpt.HasValue )
{
//log.trace( $"{Thread.CurrentThread.Name} Processed {m_processed} acts" );
/*
m_state = State.Waiting;
Semaphore.Wait();
m_state = State.Active;
m_processed = 0;
*/
return;
}
var act = actOpt.ValueOrDefault();
m_debugCurrentAct = act;
// @@@ TODO Put a timer around this and make sure any particular act is shorter than that. Probably 1ms and 5ms.
act.Fn();
++m_processed;
}
/*
public void kick()
{
Semaphore.Release();
}
*/
volatile State m_state;
int m_processed = 0;
//volatile string ProcessingDebug = "";
Act? m_debugCurrentAct = null;
}
}

View File

@ -1,308 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Optional;
using System.Diagnostics.CodeAnalysis;
namespace db
{
struct TimedAction : IComparable<TimedAction>
{
public long when;
public Act act;
public TimedAction( long when, Act act )
{
this.when = when;
this.act = act;
}
public int CompareTo( TimedAction other )
{
return when.CompareTo( other.when );
}
public override bool Equals( object obj )
{
return obj is TimedAction action &&
when == action.when &&
EqualityComparer<Act>.Default.Equals( act, action.act );
}
public override int GetHashCode()
{
var hc = when.GetHashCode() ^ act.GetHashCode();
return hc;
}
}
public class SystemCfg : lib.Config
{
public readonly float Cores = 1;
}
public class System<TID, T> where T : IID<TID>
{
//public static System Current => s_system;
public SemaphoreSlim ActsExist => m_actsExist;
public DB<TID, T> DB { get; private set; }
public bool Running { get; private set; }
public System( res.Ref<SystemCfg> cfg, DB<TID, T> db )
{
m_cfg = cfg;
DB = db;
var procCount = Environment.ProcessorCount;
//Exact comparison
if( m_cfg.res.Cores != 0.0f )
{
//If its less than 1, then use it as a multiplier
if( m_cfg.res.Cores < 0.0f )
{
procCount = Environment.ProcessorCount - (int)m_cfg.res.Cores;
}
else if( m_cfg.res.Cores < 1.0f )
{
procCount = (int)( (float)Environment.ProcessorCount * m_cfg.res.Cores );
}
else
{
procCount = (int)m_cfg.res.Cores;
}
}
log.info( $"Running {procCount} cores out of a total cores {Environment.ProcessorCount} via a config Cores value of {m_cfg.res.Cores}" );
Processor<TID, T>[] procs = new Processor<TID, T>[procCount];
for( var i = 0; i < procCount; ++i )
{
var proc = new Processor<TID, T>( db, this );
procs[i] = proc;
}
m_processors = m_processors.AddRange( procs );
Running = true;
}
public void forcedThisTick( Act act )
{
m_current.Add( act );
m_actsExist.Release();
}
public void next( Act act )
{
m_next.Add( act );
}
//Most things dont need accurate next frame processing, so split them between the next frame N frames
const double s_variance = 1.0 / 15.0;
public void future( Act act, double future, double maxVariance = s_variance )
{
//m_actions.Add( act );
var variance = m_rand.NextDouble() * maxVariance;
var nextTime = future + variance;
if( nextTime < 1.0 / 60.0 )
{
next( act );
return;
}
var ts = TimeSpan.FromSeconds( nextTime );
var tsTicks = ts.Ticks;
// @@@ TIMING Should we use a fixed time at the front of the frame for this?
var ticks = tsTicks + DateTime.Now.Ticks;
var ta = new TimedAction( ticks, act );
var newFuture = m_futureActions.Add( ta );
Interlocked.Exchange( ref m_futureActions, newFuture );
}
public void start()
{
int count = 0;
foreach( var p in m_processors )
{
var start = new ThreadStart( p.run );
var th = new Thread( start );
th.Name = $"Processor_{count}";
th.Start();
++count;
}
}
public void tick()
{
//Debug.Assert( m_current.IsEmpty );
addTimedActions();
var current = m_current;
m_current = m_next;
m_next = current;
while( !m_current.IsEmpty )
{
m_actsExist.Release();
}
/*
foreach( var proc in m_processors )
{
//Debug.Assert( proc.State == State.Waiting );
proc.kick();
}
*/
}
/*
public void wait_blah( int targetMs, int maxMs )
{
var done = 0;
var start = DateTime.Now;
var delta = start - start;
while( done < m_processors.Count && delta.TotalMilliseconds < maxMs )
{
done = 0;
foreach( var proc in m_processors )
{
if( proc.State != State.Active )
{
++done;
}
}
delta = DateTime.Now - start;
}
if( done != m_processors.Count )
{
log.warn( $"Processing took significantly too long {delta.TotalSeconds}sec." );
foreach( var proc in m_processors )
{
Act debugAct = proc.DebugCurrentAct;
if( proc.State == State.Active )
{
log.warn( $"Proc is still running\n{debugAct.Path}({debugAct.Line}): In method {debugAct.Member}" );
// @@@ TODO Should we kill the procedure? Let it continue to run?
}
}
}
if( delta.TotalMilliseconds > targetMs )
{
log.warn( $"Missed our target {delta.TotalMilliseconds} framerate." );
}
}
//*/
public void addTimedActions()
{
var sortedFutureActions = m_futureActions.Sort();
var future = TimeSpan.FromMilliseconds( 33.33333 );
var time = DateTime.Now + future;
foreach( var action in sortedFutureActions )
{
if( action.when < time.Ticks )
{
next( action.act );
var newActions = m_futureActions.Remove( action );
Interlocked.Exchange( ref m_futureActions, newActions );
}
else
{
break;
}
}
}
public void stopRunning()
{
Running = false;
}
internal Option<Act> getNextAct()
{
if( m_current.TryTake( out Act res ) )
{
return res.Some();
}
m_actsExist.Wait();
return Option.None<Act>();
}
res.Ref<SystemCfg> m_cfg;
SemaphoreSlim m_actsExist = new SemaphoreSlim( 0 );
Random m_rand = new Random();
ConcurrentBag<Act> m_current = new ConcurrentBag<Act>();
ConcurrentBag<Act> m_next = new ConcurrentBag<Act>();
// @@ TODO Keep an eye on the timing of this.
ImmutableList<TimedAction> m_futureActions = ImmutableList<TimedAction>.Empty;
/*
TimedAction[] m_sortedFutureActions = new TimedAction[16 * 1024];
int m_sfaStart = 0;
int m_sfaEnd = 0;
*/
ImmutableList<Processor<TID, T>> m_processors = ImmutableList<Processor<TID, T>>.Empty;
//private static System s_system;
}
}

View File

@ -1,98 +0,0 @@
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
namespace exp;
abstract public record class Exp<T> : io.Versioned<Exp<T>>
{
protected Exp()
:
base()
{
}
abstract public T exec();
}
public record class ConstantExp<T>( T value ) : Exp<T>
{
public override T exec() => value;
}
public record class VarExp<T> : Exp<T>
{
public T val = default!;
public VarExp()
{
}
public override T exec() => val;
public VarExp<T> set( T newVal )
{
return this with { val = newVal };
}
}
public ref struct RefHolder<T>
{
public T val = default!;
public RefHolder()
{
}
public RefHolder( T initial )
{
val = initial;
}
}
/*
public record class Op<T>( EntityId id, Func<Entity, T> fn,
[CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = ""
) : Exp<T>
{
public override T exec()
{
var ent = ent.Entity.Get( id );
if( ent == null )
throw new System.Exception( $"Op<{typeof(T).Name}>: Entity {id} not found" );
return fn( ent );
}
}
*/
public record class StackExp<T>( VarExp<T> BaseVal ) : Exp<T>
{
public VarExp<T> ModValue = BaseVal;
public ImmutableArray<Exp<T>> Adds = ImmutableArray<Exp<T>>.Empty;
public ImmutableArray<Exp<T>> Mults = ImmutableArray<Exp<T>>.Empty;
public override T exec()
{
return ModValue.exec();
}
}
/// //

View File

@ -1,64 +0,0 @@

using System;
using System.Runtime.CompilerServices;
namespace fsm;
public class Context
{
}
public class State<T, CTX>
where T : State<T, CTX>
where CTX : Context
{
virtual public void onEnter( CTX ctx, State<T, CTX> oldState )
{
}
virtual public void onExit( CTX ctx, State<T, CTX> newState )
{
}
}
public class FSM<T, CTX, ST>
where T : FSM<T, CTX, ST>
where CTX : Context
where ST : State<ST, CTX>
{
public CTX Context { get; private set; }
public ST State { get; private set; }
public FSM( CTX context, ST state )
{
Context = context;
State = state;
State.onEnter( Context, state );
}
public void transition( ST newState, string reason = "",
[CallerMemberName] string member = "",
[CallerFilePath] string path = "",
[CallerLineNumber] int line = 0 )
{
log.debug( $"{GetType().Name} switching to {newState.GetType().Name}from {State.GetType().Name} bcs {reason} Code {log.relativePath( path )}:({line}): {member}" );
State.onExit( Context, newState );
newState.onEnter( Context, State );
State = newState;
}
}
/*
Im going to preface this with, I use FSMs everywhere for quite a few things.
*/

View File

@ -1,2 +0,0 @@
using System;
using System.Collections.Generic;

View File

@ -1,182 +0,0 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// S H A R P L I B
//
/// // (c) 2003..2025
using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.Collections.Immutable;
namespace ser;
public record CodeGenConfig : io.Recorded<CodeGenConfig>
{
// Whitelists (if needed, otherwise rely on attributes/defaults)
public ImmutableDictionary<string, ImmutableList<string>> WLProps { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
public ImmutableDictionary<string, ImmutableList<string>> WLFields { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
// Default member types to process
public ser.Types TypesDefault { get; init; } = ser.Types.Fields | ser.Types.Props;
// How to handle backing fields (might be less relevant for code gen)
public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Regular;
public static CodeGenConfig Default { get; } = new CodeGenConfig();
}
public record GenMemberMeta(
MemberInfo Info,
Type Type,
string Name, // Name for code generation (usually original)
bool IsPrimitive,
bool IsCollection,
Type? CollectionElementType,
bool HasDo,
bool HasDont
);
public record TypeStructureInfo(
Type Type,
List<GenMemberMeta> Members,
bool IsValueType,
bool IsCollection
);
public class TypeStructureAnalyzer
{
private readonly ConcurrentDictionary<Type, TypeStructureInfo> _cache = new();
private readonly CodeGenConfig _cfg;
public TypeStructureAnalyzer( CodeGenConfig cfg ) => _cfg = cfg;
public TypeStructureInfo Get( Type type ) => _cache.GetOrAdd( type, BuildTypeInfo );
private TypeStructureInfo BuildTypeInfo( Type type )
{
var members = new List<GenMemberMeta>();
var typesTodo = type.GetCustomAttribute<ser.Ser>( true )?.Types ?? _cfg.TypesDefault;
bool doFields = typesTodo.HasFlag( ser.Types.Fields );
bool doProps = typesTodo.HasFlag( ser.Types.Props );
// Track processed names to avoid duplicates (e.g., field + prop)
var processedNames = new HashSet<string>();
// Process Properties First (often preferred interface)
if( doProps )
{
foreach( var pi in refl.GetAllProperties( type ) )
{
if( ProcessMember( pi, false, false, new HashSet<string>(), false, members ) )
{
processedNames.Add( pi.Name );
}
}
}
// Process Fields, avoiding those already covered by properties
if( doFields )
{
foreach( var fi in refl.GetAllFields( type ) )
{
var (isBacking, propName) = IsBackingField( fi );
string nameToTest = isBacking ? propName : fi.Name;
if( !processedNames.Contains( nameToTest ) )
{
if( ProcessMember( fi, false, false, new HashSet<string>(), false, members ) )
{
processedNames.Add( nameToTest );
}
}
}
}
return new TypeStructureInfo(
type,
members,
type.IsValueType,
typeof( IEnumerable ).IsAssignableFrom( type ) && type != typeof( string )
);
}
private bool ProcessMember( MemberInfo mi, bool filter, bool doImpls, HashSet<string> whitelist, bool isImm, List<GenMemberMeta> members )
{
var (hasDo, hasDont, propName) = GetMemberAttributes( mi, out var actualMiForAtts );
if( hasDont )
return false;
if( mi.GetCustomAttribute<NonSerializedAttribute>( true ) != null )
return false;
if( mi.Name.Contains( "k__BackingField" ) && !propName.Any() )
return false; // Skip if backing but no prop found
string name = string.IsNullOrEmpty( propName ) ? mi.Name : propName;
// Add filtering logic if needed (based on whitelist, etc.)
var type = ( mi is FieldInfo fi ) ? fi.FieldType : ( (PropertyInfo)mi ).PropertyType;
bool isCollection = typeof( IEnumerable ).IsAssignableFrom( type ) && type != typeof( string );
Type? elementType = isCollection ? GetElementType( type ) : null;
bool isPrimitive = Type.GetTypeCode( type ) != TypeCode.Object && !isCollection;
members.Add( new GenMemberMeta(
mi, type, name, isPrimitive, isCollection, elementType, hasDo, hasDont
) );
return true;
}
private (bool, string) IsBackingField( FieldInfo fi )
{
if( fi.Name.StartsWith( "<" ) && fi.Name.EndsWith( "BackingField" ) )
{
var gtIndex = fi.Name.IndexOf( '>' );
if( gtIndex > 1 )
{
return (true, fi.Name.Substring( 1, gtIndex - 1 ));
}
}
return (false, "");
}
private (bool hasDo, bool hasDont, string propName) GetMemberAttributes( MemberInfo mi, out MemberInfo actualMi )
{
actualMi = mi;
string propName = "";
if( mi is FieldInfo fi && IsBackingField( fi ).Item1 )
{
propName = IsBackingField( fi ).Item2;
var propInfo = mi.DeclaringType?.GetProperty( propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
if( propInfo != null )
actualMi = propInfo;
}
else if( mi is PropertyInfo )
{
propName = mi.Name;
}
return (
actualMi.GetCustomAttribute<ser.Do>() != null,
actualMi.GetCustomAttribute<ser.Dont>() != null,
propName
);
}
private Type GetElementType( Type collectionType )
{
if( collectionType.IsArray )
return collectionType.GetElementType()!;
if( collectionType.IsGenericType )
return collectionType.GetGenericArguments().Last(); // Usually last (e.g., List<T>, Dict<K,V>)
return typeof( object ); // Fallback
}
// Add GetFilters and FilterField if needed, or simplify as above
}

View File

@ -1,74 +0,0 @@
using System.Text;
using System.Collections.Generic;
using System;
using System.Linq;
namespace ser;
public abstract class CodeGenerator
{
protected StringBuilder _sb = new StringBuilder();
protected int _indent = 0;
protected TypeStructureAnalyzer _analyzer;
protected CodeGenConfig _config;
protected HashSet<Type> _generatedTypes = new(); // Track to avoid re-generating
public CodeGenerator( CodeGenConfig config )
{
_config = config;
_analyzer = new TypeStructureAnalyzer( config );
}
// Main entry point
public string Generate( Type type, string ns = "GeneratedCode" )
{
_sb.Clear();
WriteLine( "using System;" );
WriteLine( "using System.Collections.Generic;" );
WriteLine( "using System.Linq;" );
WriteLine( "" );
WriteLine( $"namespace {ns};" );
WriteLine( "" );
GenerateForType( type );
return _sb.ToString();
}
// Core generation logic - needs to be recursive for dependencies
protected virtual void GenerateForType( Type type )
{
if( type == null || !CanGenerateFor( type ) || _generatedTypes.Contains( type ) )
return;
_generatedTypes.Add( type );
var info = _analyzer.Get( type );
// Generate dependencies first
foreach( var member in info.Members )
{
GenerateForType( member.Type );
if( member.IsCollection && member.CollectionElementType != null )
{
GenerateForType( member.CollectionElementType );
}
}
// Generate the actual code
GenerateClassHeader( info );
BeginBlock();
GenerateClassBody( info );
EndBlock();
}
// Abstract methods to be implemented by specific generators
protected abstract void GenerateClassHeader( TypeStructureInfo info );
protected abstract void GenerateClassBody( TypeStructureInfo info );
protected abstract bool CanGenerateFor( Type type ); // Check if we should generate for this type
// Helper methods
protected void WriteLine( string line = "" ) => _sb.AppendLine( new string( '\t', _indent ) + line );
protected void BeginBlock() { WriteLine( "{" ); _indent++; }
protected void EndBlock() { _indent--; WriteLine( "}" ); }
protected string GetTypeName( Type t ) => t.IsGenericType
? $"{t.Name.Split( '`' )[0]}<{string.Join( ", ", t.GetGenericArguments().Select( GetTypeName ) )}>"
: t.Name; // Basic handling, needs improvement for full names/namespaces
}

View File

@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Security.Permissions;
namespace lib
{
[Serializable]
public class SerializableDictionary<TKey, TVal>: Dictionary<TKey, TVal>, IXmlSerializable, ISerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableDictionary()
{
}
public SerializableDictionary( IDictionary<TKey, TVal> dictionary )
: base( dictionary )
{
}
public SerializableDictionary( IEqualityComparer<TKey> comparer )
: base( comparer )
{
}
public SerializableDictionary( int capacity )
: base( capacity )
{
}
public SerializableDictionary( IDictionary<TKey, TVal> dictionary, IEqualityComparer<TKey> comparer )
: base( dictionary, comparer )
{
}
public SerializableDictionary( int capacity, IEqualityComparer<TKey> comparer )
: base( capacity, comparer )
{
}
#endregion
#region ISerializable Members
protected SerializableDictionary( SerializationInfo info, StreamingContext context )
{
int itemCount = info.GetInt32("ItemCount");
for( int i = 0; i < itemCount; i++ )
{
KeyValuePair<TKey, TVal> kvp = (KeyValuePair<TKey, TVal>)info.GetValue(String.Format( $"Item{i}" ), typeof(KeyValuePair<TKey, TVal>));
this.Add( kvp.Key, kvp.Value );
}
}
//[SecurityPermission( SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter )]
void ISerializable.GetObjectData( SerializationInfo info, StreamingContext context )
{
info.AddValue( "ItemCount", this.Count );
int itemIdx = 0;
foreach( KeyValuePair<TKey, TVal> kvp in this )
{
info.AddValue( String.Format( $"Item{itemIdx}" ), kvp, typeof( KeyValuePair<TKey, TVal> ) );
itemIdx++;
}
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml( System.Xml.XmlWriter writer )
{
//writer.WriteStartElement(DictionaryNodeName);
foreach( KeyValuePair<TKey, TVal> kvp in this )
{
writer.WriteStartElement( ItemNodeName );
writer.WriteStartElement( KeyNodeName );
KeySerializer.Serialize( writer, kvp.Key );
writer.WriteEndElement();
writer.WriteStartElement( ValueNodeName );
ValueSerializer.Serialize( writer, kvp.Value );
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
void IXmlSerializable.ReadXml( System.Xml.XmlReader reader )
{
if( reader.IsEmptyElement )
{
return;
}
// Move past container
if( !reader.Read() )
{
throw new XmlException( "Error in Deserialization of Dictionary" );
}
//reader.ReadStartElement(DictionaryNodeName);
while( reader.NodeType != XmlNodeType.EndElement )
{
reader.ReadStartElement( ItemNodeName );
reader.ReadStartElement( KeyNodeName );
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement( ValueNodeName );
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add( key, value );
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if( valueSerializer == null )
{
valueSerializer = new XmlSerializer( typeof( TVal ) );
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if( keySerializer == null )
{
keySerializer = new XmlSerializer( typeof( TKey ) );
}
return keySerializer;
}
}
#endregion
#region Private Members
private XmlSerializer keySerializer = null;
private XmlSerializer valueSerializer = null;
#endregion
}
}

View File

@ -1,677 +0,0 @@
using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Diagnostics;
//using System.Globalization;
//using System.ComponentModel;
using System.Runtime.Serialization;
namespace lib
{
/// <summary>
///
/// </summary>
public class VersionFormatter: IFormatter
{
public enum ETypes
{
Array,
Int32,
Ref,
Object,
EndObject,
Single,
Double,
Char,
String,
Boolean,
EndStream,
}
public VersionFormatter()
{
//
// TODO: Add constructor logic here
//
}
#region Useless
public ISurrogateSelector SurrogateSelector
{
get
{
return null;
}
set
{
}
}
public SerializationBinder Binder
{
get
{
return null;
}
set
{
}
}
public StreamingContext Context
{
get
{
return new StreamingContext();
}
set
{
}
}
#endregion Useless
Queue m_objectsToBeDeserialized = new Queue();
Hashtable m_alreadyDeserialzied = new Hashtable();
//int m_GUID = 0;
#region Serialize
public void Serialize( Stream stream, object obj )
{
//Default is 4k
//BufferedStream bufStream = new BufferedStream( stream );
BinaryWriter writer = new BinaryWriter( stream );
writeObject( writer, obj );
while( m_objectsToBeDeserialized.Count != 0 )
{
object objToDes = m_objectsToBeDeserialized.Dequeue();
writeObject( writer, objToDes );
}
writer.Write( (char)ETypes.EndStream );
}
void writeRefAndSched( BinaryWriter writer, object obj )
{
//if( m_alreadyDeserialzied[ obj.GetType().GetArrayRank(
if( obj == null )
{
writer.Write( 0 );
return;
}
//Now write the address.
//Bad bad. Need to do this correctly.
int objRef = obj.GetHashCode();
writer.Write( objRef );
if( m_alreadyDeserialzied[obj] == null )
{
m_alreadyDeserialzied[obj] = obj;
m_objectsToBeDeserialized.Enqueue( obj );
}
}
void dispatchWrite( BinaryWriter writer, object parentObj, FieldInfo fi )
{
string typeName = fi.FieldType.Name;
string name = fi.Name;
if( fi.IsNotSerialized )
{
return;
}
if( fi.FieldType.IsArray )
{
writer.Write( (char)ETypes.Array );
writer.Write( name.GetHashCode() );
writeArray( writer, (Array)fi.GetValue( parentObj ) );
}
else if( ( fi.FieldType.IsClass || fi.FieldType.IsInterface ) && typeName != "String" )
{
writer.Write( (char)ETypes.Ref );
writer.Write( name.GetHashCode() );
writeRefAndSched( writer, fi.GetValue( parentObj ) );
}
else if( fi.FieldType.IsEnum )
{
writer.Write( (char)ETypes.Int32 );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToInt32( fi.GetValue( parentObj ) ) );
}
else
{
switch( typeName )
{
case "Int32":
writer.Write( (char)ETypes.Int32 );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToInt32( fi.GetValue( parentObj ) ) );
break;
case "Single":
writer.Write( (char)ETypes.Single );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToSingle( fi.GetValue( parentObj ) ) );
break;
case "Double":
writer.Write( (char)ETypes.Double );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToDouble( fi.GetValue( parentObj ) ) );
break;
case "Char":
writer.Write( (char)ETypes.Char );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToChar( fi.GetValue( parentObj ) ) );
break;
case "String":
writer.Write( (char)ETypes.String );
writer.Write( name.GetHashCode() );
write( writer, Convert.ToString( fi.GetValue( parentObj ) ) );
break;
case "Boolean":
writer.Write( (char)ETypes.Boolean );
writer.Write( name.GetHashCode() );
writer.Write( Convert.ToBoolean( fi.GetValue( parentObj ) ) );
break;
default:
Console.WriteLine( "VersionFormatter does not understand type " + typeName );
break;
}
}
}
void writeArray( BinaryWriter writer, Array array )
{
if( array == null )
{
writer.Write( (int)-1 );
return;
}
writer.Write( array.Length );
foreach( object obj in array )
{
writeRefAndSched( writer, obj );
}
}
void getAllFields( object obj, ArrayList list )
{
Type t = obj.GetType();
while( t != null )
{
FieldInfo[] fiArr = t.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly );
list.AddRange( fiArr );
t = t.BaseType;
}
}
void writeObject( BinaryWriter writer, object obj )
{
Type objType = obj.GetType();
writer.Write( (char)ETypes.Object );
writer.Write( objType.FullName );
int objRef = obj.GetHashCode();
writer.Write( objRef );
ArrayList list = new ArrayList();
getAllFields( obj, list );
foreach( FieldInfo fi in list )
{
dispatchWrite( writer, obj, fi );
}
writer.Write( (char)ETypes.EndObject );
}
void write<TType>( BinaryWriter wr, TType val )
{
//wr.Write( val );
}
/*
void writeInt( BinaryWriter writer, int val )
{
writer.Write( val );
}
void writeSingle( BinaryWriter writer, float val )
{
writer.Write( val );
}
void writeDouble( BinaryWriter writer, double val )
{
writer.Write( val );
}
void writeChar( BinaryWriter writer, char val )
{
writer.Write( val );
}
void writeString( BinaryWriter writer, string val )
{
writer.Write( val );
}
void writeBool( BinaryWriter writer, bool val )
{
writer.Write( val );
}
*/
#endregion Serialize
#region Deserialize
class Fixup
{
public Fixup( int guid, object obj, FieldInfo fi )
{
m_guid = guid;
m_obj = obj;
m_fi = fi;
}
public Fixup( int guid, object obj, int index )
{
m_guid = guid;
m_obj = obj;
m_index = index;
}
public readonly int m_guid = 0;
public readonly object m_obj = null;
public readonly FieldInfo m_fi = null;
public readonly int m_index= -1;
}
Hashtable m_mapGUIDToObject = new Hashtable();
ArrayList m_fixupList = new ArrayList();
ArrayList m_desObjects = new ArrayList();
public object Deserialize( Stream stream )
{
BinaryReader reader = new BinaryReader( stream );
object objRoot = null;
//Read in the first object.
{
ETypes type = (ETypes)reader.ReadChar();
Debug.Assert( type == ETypes.Object );
objRoot = readObject( reader );
m_desObjects.Add( objRoot );
}
bool readObjects = true;
while( readObjects )
{
ETypes type = (ETypes)reader.ReadChar();
Debug.Assert( type == ETypes.Object || type == ETypes.EndStream );
if( type == ETypes.Object )
{
object obj = readObject( reader );
m_desObjects.Add( obj );
}
else
{
Debug.Assert( type == ETypes.EndStream );
readObjects = false;
}
}
foreach( Fixup fu in m_fixupList )
{
//Fixup fix = m_fixups[
object obj = m_mapGUIDToObject[ fu.m_guid ];
if( obj != null )
{
if( fu.m_fi != null )
{
fu.m_fi.SetValue( fu.m_obj, obj );
}
else
{
Debug.Assert( fu.m_index >= 0 );
object []array = (object [])fu.m_obj;
array[fu.m_index] = obj;
}
}
else
{
Console.WriteLine( "Obj to ref is null." );
}
}
foreach( object obj in m_desObjects )
{
if( typeof( IDeserializationCallback ).IsAssignableFrom( obj.GetType() ) )
{
IDeserializationCallback desCB = (IDeserializationCallback)obj;
if( desCB != null )
{
desCB.OnDeserialization( this );
}
}
}
return objRoot;
}
bool dispatchRead( BinaryReader reader, object obj, Hashtable ht )
{
//Read the type
ETypes type = (ETypes)reader.ReadChar();
if( type == ETypes.EndObject )
{
return false;
}
int nameHash = reader.ReadInt32();
FieldInfo fi = (FieldInfo)ht[ nameHash ];
if( fi == null )
{
Console.WriteLine( "Field no longer exists" );
}
try
{
switch( type )
{
case ETypes.Array:
readArray( reader, obj, fi );
break;
case ETypes.Int32:
readInt( reader, obj, fi );
break;
case ETypes.Single:
readSingle( reader, obj, fi );
break;
case ETypes.Double:
readDouble( reader, obj, fi );
break;
case ETypes.Char:
readChar( reader, obj, fi );
break;
case ETypes.Boolean:
readBool( reader, obj, fi );
break;
case ETypes.String:
readString( reader, obj, fi );
break;
case ETypes.Ref:
readRef( reader, obj, fi );
break;
case ETypes.Object:
readObject( reader );
break;
default:
Debug.Fail( "Unknown type on read." );
break;
}
}
catch( Exception ex )
{
Console.WriteLine( "Exception: " + ex.Message );
Console.WriteLine( "Stack: " + ex.StackTrace );
}
return true;
}
object createObject( string objTypeName )
{
Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies();
foreach( Assembly a in ass )
{
Type t = a.GetType( objTypeName );
if( t != null )
{
object obj = FormatterServices.GetUninitializedObject( t );
if( obj != null )
{
return obj;
}
}
}
return null;
}
object readObject( BinaryReader reader )
{
//ETypes type = (ETypes)reader.ReadChar();
//Debug.Assert( type == ETypes.Object, "Expecting type Object" );
string objTypeName = reader.ReadString();
int objGUID = reader.ReadInt32();
try
{
object obj = createObject( objTypeName );
m_mapGUIDToObject[objGUID] = obj;
ArrayList list = new ArrayList();
Hashtable ht = new Hashtable();
if( obj != null )
{
getAllFields( obj, list );
foreach( FieldInfo fi in list )
{
ht[fi.Name.GetHashCode()] = fi;
}
}
while( dispatchRead( reader, obj, ht ) )
{
}
return obj;
}
catch( Exception ex )
{
Console.WriteLine( "Exception: " + ex.Message );
}
return null;
}
void readArray( BinaryReader reader, object obj, FieldInfo fi )
{
int length = reader.ReadInt32();
if( length < 0 )
{
if( fi == null )
return;
fi.SetValue( obj, null );
return;
}
object[] array = new object[length];
if( fi != null )
{
fi.SetValue( obj, array );
}
for( int i = 0; i < length; ++i )
{
int val = reader.ReadInt32();
//m_fixups[ val ] = new Fixup( obj, fi );
if( fi != null )
{
m_fixupList.Add( new Fixup( val, array, i ) );
}
}
}
void readRef( BinaryReader reader, object obj, FieldInfo fi )
{
int val = reader.ReadInt32();
//m_fixups[ val ] = new Fixup( obj, fi );
m_fixupList.Add( new Fixup( val, obj, fi ) );
}
void readInt( BinaryReader reader, object obj, FieldInfo fi )
{
int val = reader.ReadInt32();
if( fi == null )
return;
if( !fi.FieldType.IsEnum )
{
fi.SetValue( obj, val );
}
else
{
object enumVal = Enum.Parse( fi.FieldType, val.ToString() );
fi.SetValue( obj, Convert.ChangeType( enumVal, fi.FieldType ) );
}
}
void readSingle( BinaryReader reader, object obj, FieldInfo fi )
{
float val = reader.ReadSingle();
if( fi == null )
return;
fi.SetValue( obj, val );
}
void readDouble( BinaryReader reader, object obj, FieldInfo fi )
{
double val = reader.ReadDouble();
if( fi == null )
return;
fi.SetValue( obj, val );
}
void readChar( BinaryReader reader, object obj, FieldInfo fi )
{
char val = reader.ReadChar();
if( fi == null )
return;
fi.SetValue( obj, val );
}
void readString( BinaryReader reader, object obj, FieldInfo fi )
{
string val = reader.ReadString();
if( fi == null )
return;
fi.SetValue( obj, val );
}
void readBool( BinaryReader reader, object obj, FieldInfo fi )
{
bool val = reader.ReadBoolean();
if( fi == null )
return;
fi.SetValue( obj, val );
}
#endregion Deserialize
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,516 +0,0 @@
using lib;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using TraceKeywords = Microsoft.Diagnostics.Tracing.Parsers.ClrTraceEventParser.Keywords;
public static class LogGCExt
{
public static string MethodInfo( TraceEvent te, string prefix = "Method" )
{
return $"{te.PayloadStringByName( $"{prefix}Namespace" )}.{te.PayloadStringByName( $"{prefix}Name" )}{te.PayloadStringByName( $"{prefix}Signature" )}";
}
public static string Get( this TraceEvent te, string name, string def = "" ) => te.PayloadStringByName( name ) ?? def;
public static T Get<T>( this TraceEvent te, string name, T def = default )
{
var index = te.PayloadIndex( name );
if( index <= 0 )
return default;
var value = te.PayloadValue( index );
if( value.GetType() != typeof( T ) )
{
log.warn( $"In {te.ID} Payload {name} is type {value.GetType().FriendlyName()} not {typeof( T ).FriendlyName()}" );
return default;
}
return (T)value;
}
}
public class LogGC
{
static ImmutableHashSet<string> blacklist;
static ImmutableHashSet<string> stacklist;
static LogGC()
{
blacklist = ImmutableHashSet.Create(
"FAKE_TEST",
"GC/BulkMovedObjectRanges",
/*
"GC/BulkSurvivingObjectRanges",
"GC/BulkRootStaticVar",
"GC/BulkNode",
"GC/BulkEdge",
"GC/BulkRootEdge",
"GC/FinalizeObject",
"GC/SetGCHandle",
"GC/DestoryGCHandle",
"GC/MarkWithType",
"GC/SuspendEEStart",
"GC/SuspendEEStop",
"GC/RestartEEStart",
"GC/RestartEEStop",
"GC/FinalizersStart",
// "GC/FinalizersStop", //Keep this one since it has details
"GC/GenerationRange",
"GC/FitBucketInfo",
"TieredCompilation/BackgroundJitStart",
"Type/BulkType",
"TypeLoad/Start",
"Method/R2RGetEntryPointStart",
"Method/MethodDetails",
"Method/MemoryAllocatedForJitCode",
"Method/JittingStarted",
"Method/ILToNativeMap",
"ThreadPoolWorkerThread/Wait",
"ILStub/StubGenerated"
*/
"FAKE_END_IS_NOTHING"
);
stacklist = ImmutableHashSet.Create( "{TEST_ITEM}" );
log.endpointForCat( "Method/MemoryAllocatedForJitCode", log.Endpoints.File );
log.endpointForCat( "TypeLoad/Stop", log.Endpoints.File );
log.endpointForCat( "GC/BulkRootStaticVar", log.Endpoints.File );
log.endpointForCat( "GC/BulkNode", log.Endpoints.File );
log.endpointForCat( "GC/BulkRootStaticVar", log.Endpoints.File );
}
static public void RegisterObjectId( object obj )
{
/*
var gchWeak = GCHandle.Alloc( obj, GCHandleType.Weak );
var gchNRML = GCHandle.Alloc( obj, GCHandleType.Normal );
var intPtrWeak = GCHandle.ToIntPtr( gchWeak );
var intPtrNRML = GCHandle.ToIntPtr( gchNRML );
//var intPtr = Marshal.GetIUnknownForObject( obj );
var intPtr = 0; //gchNRML.AddrOfPinnedObject();
//Marshal.GetTypedObjectForIUnknown()
// 0x00000003400111C0
// 0000000000000000
log.info( $"Log ObjectIDs: 0x{intPtrNRML.ToString("X"):0000000000000000} 0x{intPtrWeak.ToString("X"):0000000000000000}" );
//*/
}
public static Action<TraceEvent> LogCategoryFunc( string catIn )
{
return ( TraceEvent te ) =>
{
var cat = catIn;
if( blacklist.Contains( te.EventName ) )
return;
{
var methodBeingCompiledNamespace = te.Get( "MethodBeingCompiledNamespace" );
if( ( methodBeingCompiledNamespace.StartsWith( "Microsoft" ) || methodBeingCompiledNamespace.StartsWith( "System" ) ) )
return;
}
{
var methodNamespace = te.PayloadStringByName( "MethodNamespace" ) ?? "";
if( ( methodNamespace.StartsWith( "Microsoft" ) || methodNamespace.StartsWith( "System" ) ) )
return;
}
{
var ns = te.PayloadStringByName( "TypeName" ) ?? "";
if( ( ns.StartsWith( "Microsoft" ) || ns.StartsWith( "System" ) ) )
return;
}
{
var ns = te.PayloadStringByName( "TypeLoad/Stop" ) ?? "";
if( ns.StartsWith( "Godot" ) )
return;
}
{
var payloadIndex = te.PayloadIndex( "count" );
if( payloadIndex > 0 )
{
var count = (int)te.PayloadValue( payloadIndex );
if( count > 16 )
return;
}
}
//<Event EventName="Method/JittingStarted" MethodILSize="0x00000059" MethodNamespace="NilEvent" MethodName="DebugString" MethodSignature="instance class System.String ()" ClrInstanceID="0"/>
//<Event EventName="Method/LoadVerbose" MethodStartAddress="0x00000003045736B8" MethodSize="0x000001BC" MethodToken="0x060001BB" MethodFlags="Jitted" MethodNamespace="NilEvent" MethodName="DebugString" MethodSignature="instance class System.String ()" ClrInstanceID="0" ReJITID="0x00000000" OptimizationTier="MinOptJitted"/>
//<Event EventName="Method/InliningFailed" MethodBeingCompiledNamespace="dynamicClass" MethodBeingCompiledName="InvokeStub_EventAttribute.set_Message" MethodBeingCompiledNameSignature="class System.Object (class System.Object,class System.Object,int*)" InlinerNamespace="dynamicClass" InlinerName="InvokeStub_EventAttribute.set_Message" InlinerNameSignature="class System.Object (class System.Object,class System.Object,int*)" InlineeNamespace="System.Diagnostics.Tracing.EventAttribute" InlineeName="set_Message" InlineeNameSignature="instance void (class System.String)" FailAlways="False" FailReason="uses NextCallReturnAddress intrinsic" ClrInstanceID="0"/>
if( !te.EventName.StartsWith( "EventID" ) )
{
var logDetails = "";
// Custom event displays
if( te.EventName == "Method/LoadVerbose" )
{
var optTier = te.Get( "OptimizationTier" );
var methodNamespace = te.Get( "MethodNamespace" );
if( optTier == "MinOptJitted" )
return;
if( methodNamespace.StartsWith( "FastSerialization" ) )
return;
log.info( $"{optTier} {LogGCExt.MethodInfo( te )}", cat: te.EventName );
return;
//logDetails = "| Details: ";
}
else if( te.EventName.StartsWith( "Method/Inlining" ) )
{
var methodNamespace = te.Get( "MethodBeingCompiledNamespace" );
if( methodNamespace.StartsWith( "FastSerialization" ) )
return;
log.info( $"Inlining {te.Get( "FailReason" )} {te.Get( "OptimizationTier" )} {LogGCExt.MethodInfo( te, "MethodBeingCompiled" )}", cat: te.EventName );
return;
//logDetails = "| Details: ";
}
else if( te.EventName == "Type/BulkType" )
{
var val = te.ToString();
XmlDocument doc = new();
var stream = new MemoryStream();
var writer = new StreamWriter( stream );
writer.Write( val );
writer.Flush();
stream.Position = 0;
doc.Load( stream );
var root = doc.DocumentElement;
XmlNodeList children = root.ChildNodes;
foreach( var child in children )
{
var node = child as XmlElement;
log.info( $"Child: {node.Name}" );
}
return;
}
else if( te.EventName == "TypeLoad/Stop" )
{
/*
var typeName = te.PayloadStringByName( "TypeName" );
if( typeName.StartsWith( "Godot." ) )
return;
log.info( $"{typeName} Level: {te.PayloadStringByName( "LoadLevel" )}", cat: te.EventName );
//*/
return;
//logDetails = "| Details: ";
}
else if( te.EventName.StartsWith( "Method/R2RGetEntryPoint" ) )
{
log.info( $"{LogGCExt.MethodInfo( te )} Entry: {te.PayloadStringByName( "EntryPoint" )}", cat: te.EventName );
return;
//logDetails = "| Details: ";
}
else if( te.EventName.StartsWith( "Contention/LockCreated" ) )
{
// "Contention/Start" AssociatedObjectID
// System.Runtime.InteropServices.GCHandle.FromIntPtr(
var lockId = te.Get<UInt64>( "LockID" );
var objId = te.Get<UInt64>( "AssociatedObjectID" );
var testObj = Marshal.GetIUnknownForObject( objId );
var intPtrStr = te.PayloadStringByName( "AssociatedObjectID" ).Substring( 2 );
try
{
//var intPtr = Convert.ToUInt64( intPtrStr, 16 );
//var gch = System.Runtime.InteropServices.GCHandle.FromIntPtr( intPtr );
var gch = new GCHandle();
//*
var allocated = gch.IsAllocated;
var obj = gch.Target;
log.info( $"Lock {lockId} Create {objId.ToString( "X" )} {obj?.GetType()?.Name} {obj}", cat: te.EventName );
//*/
}
catch( Exception ex )
{
log.info( $"Lock {lockId} Create 0x{intPtrStr} (Could not get object) [{ex.Message}]", cat: te.EventName );
}
LogGeneric( te, "| Raw: " );
return;
//logDetails = "| Details: ";
}
else if( te.EventName.StartsWith( "Contention/Start" ) )
{
// EventName="Contention/Start" ContentionFlags="Managed" LockID="0x000000011D015CB8" AssociatedObjectID="0x00000003504554C0" LockOwnerThreadID="0"
// EventName="Contention/Stop" ContentionFlags="Managed" DurationNs="26000"
var lockId = te.Get<UInt64>( "LockID" );
var objId = te.Get<UInt64>( "AssociatedObjectID" );
var threadId = te.Get<Int64>( "LockOwnerThreadID" );
log.info( $"Lock {lockId} {te.Get( "ContentionFlags" )} in thread {threadId} on obj 0x{objId.ToString( "X" )} ", cat: te.EventName );
LogGeneric( te, "| Raw: " );
}
else if( te.EventName.StartsWith( "Contention/Stop" ) )
{
//var lockId = te.Get<int>( "LockID" );
//var objId = te.Get<IntPtr>( "AssociatedObjectID" );
//var threadId = te.Get<Int64>( "LockOwnerThreadID" );
log.info( $"Lock {{lockId}} {te.Get( "ContentionFlags" )} Duration {te.Get<double>( "DurationNs" )}ns", cat: te.EventName );
LogGeneric( te, "| Raw: " );
}
else if( te.EventName.StartsWith( "AssemblyLoader/" ) || te.EventName.StartsWith( "Loader/" ) )
{
//AssemblyLoader/Start AssemblyName AssemblyLoadContext RequestingAssemblyLoadContext AssemblyPath RequestingAssembly
//AssemblyLoader/Stop AssemblyName AssemblyLoadContext RequestingAssemblyLoadContext AssemblyPath RequestingAssembly Success ResultAssemblyName ResultAssemblyPath Cached
//Loader/AssemblyLoad AssemblyID AppDomainID AssemblyFlags FullyQualifiedAssemblyName BindingID
//AssemblyLoader/ResolutionAttempted
// AssemblyName AssemblyLoadContext Result ResultAssemblyName ResultAssemblyPath Stage ErrorMessage
//Loader/ModuleLoad ModuleID AssemblyID ModuleFlags ModuleILPath ModuleNativePath ManagedPdbSignature ManagedPdbAge ManagedPdbBuildPath
//Loader/DomainModuleLoad ModuleID AssemblyID ModuleFlags ModuleILPath ModuleNativePath AppDomainID
//AssemblyLoader/KnownPathProbed FilePath Source Result
var appDomainId = te.Get( "AppDomainID" );
var asId = te.Get( "AssemblyID" );
var asName = te.Get( "AssemblyName" );
var asPath = te.Get( "AssemblyPath" );
var asLC = te.Get( "AssemblyLoadContext" );
var reqAs = te.Get( "RequestingAssembly" );
var reqAsLC = te.Get( "RequestingAssemblyLoadContext" );
var reqAsName = te.Get( "ResultAssemblyName" );
var reqAsPath = te.Get( "ResultAssemblyPath" );
var success = te.Get( "Success" );
var cached = te.Get( "Cached" );
var errMsg = te.Get( "ErrorMessage" );
var stage = te.Get( "Stage" );
var result = te.Get( "Result" );
var source = te.Get( "Source" );
var modId = te.Get( "ModuleID" );
var modFlags = te.Get( "ModuleFlags" );
var modILPath = te.Get( "ModuleILPath" );
var modNativePath = te.Get( "ModuleNativePath" );
var manBuildPath = te.Get( "ManagedPdbBuildPath" );
var fqaName = te.Get( "FullyQualifiedAssemblyName" );
var bindingId = te.Get( "BindingID" );
}
//76 CHARACTERS
//<Event MSec= "180.2315" PID="35297" PName="Process(35297)" TID="294046"
LogGeneric( te, logDetails );
/*
if( eventData.Length > 0 )
{
Encoding enc = new UnicodeEncoding(false, false, true);
var eventDataUtf16 = enc.GetString( eventData );
log.debug( $"> {eventDataUtf16}" );
}
*/
}
else
{
/*
var payloadNames = te.PayloadNames;
var channel = te.Channel;
var formattedMsg = te.FormattedMessage;
var keywords = te.Keywords;
var source = te.Source;
var dump = te.Dump();
var dynMemberNames = te.GetDynamicMemberNames();
var dataStart = te.DataStart;
*/
var eventData = te.EventData();
//var eventDataStr = eventData.ToString();
Encoding enc = new UnicodeEncoding( false, false, true );
var eventDataUtf16 = enc.GetString( eventData );
//var safeEventData = eventDataUtf16.Replace( (char)0, '\n' );
var arrEventData = eventDataUtf16.Split( (char)0 );
var joinedEventData = string.Join( " | ", arrEventData );
//log.info( $"{te.FormattedMessage}", cat: catIn );
log.info( $"{joinedEventData}", cat: catIn );
}
};
}
private static ConcurrentDictionary<string, ImmutableHashSet<string>> s_validKeywords = new();
private static void LogGeneric( TraceEvent te, string logDetails )
{
var teStr = te.ToString().Replace( "ClrInstanceID=\"0\"", "" );
/*
*/
var startIndex = teStr.IndexOf( "EventName" );
//log.debug( $"{logDetails} {startIndex} {teStr}", cat: te.EventName );
var teStrSlice = teStr.AsSpan( startIndex, teStr.Length - ( startIndex + 1 + 1 ) );
log.debug( $"{logDetails}{teStrSlice}", cat: te.EventName );
/*
try
{
TraceCallStack? callstack = te.CallStack();
//if( callstack != null) log.trace( $"| Callstack: {callstack?.ToString()}" );
if( callstack != null )
{
log.trace( $"| Callstack:" );
log.trace( $"| {callstack.Depth}" );
log.trace( $"| {callstack.CodeAddress}" );
}
}
catch( Exception ex )
{
log.debug( $"Caught {ex.Message} while getting the callstack" );
}
//*/
}
public static void PrintRuntimeGCEvents( int processId )
{
var providers = new List<EventPipeProvider>()
{
new EventPipeProvider("Microsoft-Windows-DotNETRuntime",
EventLevel.Verbose, (long)(
TraceKeywords.GC |
TraceKeywords.Contention |
TraceKeywords.Debugger |
TraceKeywords.Exception |
TraceKeywords.GCAllObjectAllocation |
TraceKeywords.GCSampledObjectAllocationHigh |
TraceKeywords.GCSampledObjectAllocationLow |
TraceKeywords.Security |
TraceKeywords.Threading |
TraceKeywords.Type |
TraceKeywords.TypeDiagnostic |
TraceKeywords.WaitHandle |
TraceKeywords.All
) )
};
var client = new DiagnosticsClient( processId );
using( EventPipeSession session = client.StartEventPipeSession( providers, false ) )
{
var source = new EventPipeEventSource( session.EventStream );
source.Clr.GCAllocationTick += OnAllocTick;
//source.Clr.All += LogCategoryFunc( "clr" );
//source.Kernel.All += LogCategoryFunc( "kernel" );
// Doesnt seem to be too interesting.
//source.Dynamic.All += LogCategoryFunc( "dynamic" );
try
{
source.Process();
}
catch( Exception e )
{
Console.WriteLine( "Error encountered while processing events" );
Console.WriteLine( e.ToString() );
}
}
}
private static void OnAllocTick( GCAllocationTickTraceData data )
{
}
}

View File

@ -1,675 +0,0 @@
using Microsoft.Diagnostics.Symbols;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Diagnostics.Tracing;
namespace Tracing
{
/// <summary>
/// A configurable, real-time EventPipe trace monitor for cross-platform use.
/// This class uses a fluent builder pattern to attach to a .NET process,
/// configure EventPipe providers, and process events in real-time.
/// </summary>
public class EventPipeTraceMonitor : IDisposable
{
private readonly int _pid;
private int _durationSec = 10;
private ClrTraceEventParser.Keywords _clrKeywords = ClrTraceEventParser.Keywords.None;
private bool _monitorCpu = false;
private readonly List<EventPipeProvider> _customProviders = new();
private Action<TraceEvent> _userCallback;
private EventPipeSession _session;
private TraceEventSource _traceLogSource; /// CHANGED
private Task _processingTask;
private Timer _timer;
public EventPipeTraceMonitor( int processId )
{
_pid = processId;
}
// ####################################################################
// Builder Configuration Methods
// ####################################################################
public EventPipeTraceMonitor WithDuration( int seconds )
{
_durationSec = seconds;
return this;
}
/// <summary>
/// Adds a custom EventPipeProvider.
/// </summary>
public EventPipeTraceMonitor AddProvider( EventPipeProvider provider )
{
_customProviders.Add( provider );
return this;
}
// ####################################################################
// Event Selection Methods
// ####################################################################
public EventPipeTraceMonitor MonitorExceptions()
{
// EventPipe requires JIT/Loader/Stack keywords to resolve symbols
_clrKeywords |= ClrTraceEventParser.Keywords.Exception |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
ClrTraceEventParser.Keywords.Loader;
return this;
}
public EventPipeTraceMonitor MonitorModuleLoads()
{
_clrKeywords |= ClrTraceEventParser.Keywords.Loader |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.Threading |
ClrTraceEventParser.Keywords.PerfTrack |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap;
return this;
}
public EventPipeTraceMonitor MonitorCpuSamples()
{
_monitorCpu = true;
return this;
}
// ####################################################################
// Core Execution Methods
// ####################################################################
public void Start( Action<TraceEvent> onEventCallback )
{
_userCallback = onEventCallback;
var providers = new List<EventPipeProvider>( _customProviders );
if( _clrKeywords != ClrTraceEventParser.Keywords.None )
{
// Add the main CLR provider
providers.Add( new EventPipeProvider(
"Microsoft-Windows-DotNETRuntime",
EventLevel.Informational,
(long)_clrKeywords ) );
}
if( _monitorCpu )
{
// Add the CPU sampler provider
providers.Add( new EventPipeProvider(
"Microsoft-DotNETCore-SampleProfiler",
EventLevel.Informational ) );
}
if( !providers.Any() )
{
log.warn( "No trace providers configured. Monitor will not start." );
return;
}
try
{
var client = new DiagnosticsClient( _pid );
log.info( $"Starting EventPipe session on PID {_pid} for {_durationSec}s..." );
// Start the session. requestRundown=true is critical
// to get symbols for code JIT-compiled before the session started.
_session = client.StartEventPipeSession( providers, requestRundown: true );
// Set up the timer to stop the session
_timer = new Timer( delegate
{
log.info( $"Monitoring duration ({_durationSec} sec) elapsed." );
Stop();
}, null, _durationSec * 1000, Timeout.Infinite );
// Create the TraceLogSource from the session's event stream
_traceLogSource = new EventPipeEventSource( _session.EventStream );
// KEEP TraceLog.CreateFromTraceEventSession( _session );
// Register callbacks based on requested keywords
if( ( _clrKeywords & ClrTraceEventParser.Keywords.Exception ) != 0 )
_traceLogSource.Clr.ExceptionStart += OnEvent;
if( ( _clrKeywords & ClrTraceEventParser.Keywords.Loader ) != 0 )
{
_traceLogSource.Clr.LoaderModuleLoad += OnEvent;
_traceLogSource.Clr.All += OnAllEvents;
}
//if( _monitorCpu )
// _traceLogSource.CpuSpeedMHz += OnEvent;
// Start processing the stream on a background thread.
// .Process() is a blocking call that runs until the stream ends.
_processingTask = Task.Run( () =>
{
try
{/* _traceLogSource.Pro;*/ }
catch( Exception ex ) { log.error( $"Trace processing error: {ex.Message}" ); }
log.info( "Event processing finished." );
} );
}
catch( Exception ex )
{
log.error( $"Failed to start EventPipe session: {ex.Message}" );
}
}
private void OnAllEvents( TraceEvent evt )
{
log.info( $"EVENT: [PID:{evt.ProcessID}] -- {evt.EventName}" );
}
public void Stop()
{
if( _session != null )
{
log.info( "Stopping session..." );
_timer?.Dispose();
_timer = null;
// Stopping the session will end the stream,
// which causes traceLogSource.Process() to return.
_session.Stop();
_session.Dispose();
_session = null;
_traceLogSource?.Dispose();
_traceLogSource = null;
// Wait for the processing task to complete
_processingTask?.Wait( 2000 );
_processingTask = null;
}
}
public void Dispose()
{
Stop();
}
/// <summary>
/// Internal event handler.
/// </summary>
private void OnEvent( TraceEvent data )
{
// --- Pre-filtering ---
if( data.Opcode == TraceEventOpcode.DataCollectionStart )
return;
if( data is ExceptionTraceData exd && exd.ExceptionType.Length == 0 )
return;
// --- Pass to user ---
_userCallback( data );
}
}
/// <summary>
/// A configurable, real-time ETW trace monitor. (Windows-Only)
/// This class uses a fluent builder pattern to configure and run a real-time
/// TraceEventSession, processing events through a user-provided callback.
/// </summary>
public class RealTimeTraceMonitor : IDisposable
{
private TraceEventSession _session;
private int _durationSec = 10;
private string _processFilter = null;
private KernelTraceEventParser.Keywords _kernelKeywords = KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process;
private ClrTraceEventParser.Keywords _clrKeywords = ClrTraceEventParser.Keywords.None;
private KernelTraceEventParser.Keywords _kernelStackKeywords = KernelTraceEventParser.Keywords.None;
private bool _enableRundown = false;
private bool _resolveNativeSymbols = false;
private readonly List<Action<TraceLogEventSource>> _eventRegistrars = new();
private Action<TraceEvent> _userCallback;
private SymbolReader _symbolReader;
private readonly object _symbolReaderLock = new();
private TextWriter _symbolLog = new StringWriter();
private Timer _timer;
// ####################################################################
// Builder Configuration Methods
// ####################################################################
/// <summary>
/// Sets the monitoring duration in seconds.
/// </summary>
public RealTimeTraceMonitor WithDuration( int seconds )
{
_durationSec = seconds;
return this;
}
/// <summary>
/// Filters events to only include those from a process whose name contains this string.
/// </summary>
public RealTimeTraceMonitor FilterByProcess( string processName )
{
_processFilter = processName;
return this;
}
/// <summary>
/// Provides a TextWriter for detailed symbol lookup messages.
/// </summary>
public RealTimeTraceMonitor WithSymbolLog( TextWriter logWriter )
{
_symbolLog = logWriter;
return this;
}
// ####################################################################
// Event Selection Methods
// ####################################################################
/// <summary>
/// Enables monitoring for CLR Exceptions (ExceptionStart).
/// Automatically enables JIT, Loader, and Stack keywords required for symbol resolution.
/// </summary>
public RealTimeTraceMonitor MonitorExceptions()
{
_clrKeywords |= ClrTraceEventParser.Keywords.Exception |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
ClrTraceEventParser.Keywords.Loader;
_enableRundown = true;
_resolveNativeSymbols = true; // Native symbol resolution is often needed for full exception stacks
_eventRegistrars.Add( source => source.Clr.ExceptionStart += OnEvent );
return this;
}
/// <summary>
/// Enables monitoring for CLR Module Loads (LoaderModuleLoad).
/// Automatically enables JIT, Loader, and Stack keywords.
/// </summary>
public RealTimeTraceMonitor MonitorModuleLoads()
{
_clrKeywords |= ClrTraceEventParser.Keywords.Loader |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap;
_enableRundown = true;
_eventRegistrars.Add( source => source.Clr.LoaderModuleLoad += OnEvent );
return this;
}
/// <summary>
/// Enables monitoring for CPU Samples (PerfInfoSample).
/// </summary>
public RealTimeTraceMonitor MonitorCpuSamples()
{
_kernelKeywords |= KernelTraceEventParser.Keywords.Profile;
_kernelStackKeywords |= KernelTraceEventParser.Keywords.Profile;
_resolveNativeSymbols = true; // CPU samples require native symbol resolution
_eventRegistrars.Add( source => source.Kernel.PerfInfoSample += OnEvent );
return this;
}
/// <summary>
/// Enables monitoring for arbitrary Kernel events.
/// </summary>
/// <param name="events">Kernel keywords to enable.</param>
/// <param name="stackEvents">Keywords to collect stacks for.</param>
public RealTimeTraceMonitor MonitorKernelEvents( KernelTraceEventParser.Keywords events, KernelTraceEventParser.Keywords stackEvents = KernelTraceEventParser.Keywords.None )
{
_kernelKeywords |= events;
_kernelStackKeywords |= stackEvents;
if( stackEvents != KernelTraceEventParser.Keywords.None )
{
_resolveNativeSymbols = true;
}
return this;
}
/// <summary>
/// Enables monitoring for arbitrary CLR events.
/// Note: For complex events, prefer the specific Monitor* methods.
/// </summary>
/// <param name="events">CLR keywords to enable.</param>
public RealTimeTraceMonitor MonitorClrEvents( ClrTraceEventParser.Keywords events )
{
_clrKeywords |= events;
if( ( events & ClrTraceEventParser.Keywords.Stack ) != 0 )
{
_clrKeywords |= ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
ClrTraceEventParser.Keywords.Loader;
_enableRundown = true;
_resolveNativeSymbols = true;
}
return this;
}
// ####################################################################
// Core Execution Methods
// ####################################################################
/// <summary>
/// Starts the monitoring session and blocks until the duration expires or Ctrl+C is pressed.
/// </summary>
/// <param name="onEventCallback">The callback to execute for each filtered event.</param>
public void Start( Action<TraceEvent> onEventCallback )
{
if( !OperatingSystem.IsWindows() )
{
log.error( $"{nameof( RealTimeTraceMonitor )} is only supported on Windows (ETW)." );
return;
}
_userCallback = onEventCallback;
log.info( $"Starting {nameof( RealTimeTraceMonitor )} for {_durationSec} seconds." );
if( TraceEventSession.GetActiveSessionNames().Contains( "TraceLogMonitorSession" ) )
{
log.warn( "Session 'TraceLogMonitorSession' is already active. Attaching." );
}
try
{
using( _session = new TraceEventSession( "TraceLogMonitorSession", TraceEventSessionOptions.Attach ) )
{
// Set up Ctrl-C to stop the session
Console.CancelKeyPress += ( object sender, ConsoleCancelEventArgs cancelArgs ) =>
{
cancelArgs.Cancel = true;
Stop();
};
SetupProviders();
SetupSymbolReader();
using( var traceLogSource = TraceLog.CreateFromTraceEventSession( _session ) )
{
// Register all requested event callbacks
foreach( var registrar in _eventRegistrars )
{
registrar( traceLogSource );
}
// Set up a timer to stop processing after the duration
_timer = new Timer( delegate ( object state )
{
log.info( $"Monitoring duration ({_durationSec} sec) elapsed." );
Stop();
}, null, _durationSec * 1000, Timeout.Infinite );
log.info( "Processing events..." );
traceLogSource.Process();
log.info( "Event processing finished." );
}
}
}
catch( Exception ex )
{
log.error( $"Failed to start trace session: {ex.Message}" );
log.error( "This often requires Admin privileges." );
}
finally
{
_session = null;
}
}
/// <summary>
/// Stops the monitoring session.
/// </summary>
public void Stop()
{
if( _session != null )
{
log.info( "Stopping session..." );
_session.Dispose();
_session = null;
}
if( _timer != null )
{
_timer.Dispose();
_timer = null;
}
}
public void Dispose()
{
Stop();
}
// ####################################################################
// Private Helper Methods
// ####################################################################
private void SetupProviders()
{
log.info( $"Enabling Kernel Providers with keywords: {_kernelKeywords}" );
try
{
_session.EnableKernelProvider( _kernelKeywords, _kernelStackKeywords );
}
catch( Exception ex )
{
log.error( $"Failed to enable Kernel provider: {ex}" );
}
if( _clrKeywords != ClrTraceEventParser.Keywords.None )
{
log.info( $"Enabling CLR Provider with keywords: {_clrKeywords}" );
try
{
_session.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Informational,
(ulong)_clrKeywords );
}
catch( Exception ex )
{
log.error( $"Failed to enable CLR provider: {ex}" );
}
}
if( _enableRundown )
{
// Remove keywords not relevant for rundown
var rundownKeywords = ( _clrKeywords & ~( ClrTraceEventParser.Keywords.Exception | ClrTraceEventParser.Keywords.Stack ) );
rundownKeywords |= ClrTraceEventParser.Keywords.StartEnumeration;
log.info( $"Enabling CLR Rundown Provider with keywords: {rundownKeywords}" );
try
{
_session.EnableProvider(
ClrRundownTraceEventParser.ProviderGuid,
TraceEventLevel.Informational,
(ulong)rundownKeywords );
}
catch( Exception ex )
{
log.error( $"Failed to enable CLR Rundown provider: {ex}" );
}
}
}
private void SetupSymbolReader()
{
if( _resolveNativeSymbols )
{
log.info( "Setting up SymbolReader for native symbol resolution." );
var symbolPath = new SymbolPath( SymbolPath.SymbolPathFromEnvironment ).Add( SymbolPath.MicrosoftSymbolServerPath );
_symbolReader = new SymbolReader( _symbolLog, symbolPath.ToString() );
// Allow reading PDBs from "unsafe" locations (next to EXE)
_symbolReader.SecurityCheck = ( path => true );
}
}
/// <summary>
/// Primary event handler.
/// This method is called from multiple threads and must be thread-safe.
/// </summary>
private void OnEvent( TraceEvent data )
{
// --- Pre-filtering ---
if( data.Opcode == TraceEventOpcode.DataCollectionStart )
return;
if( data is ExceptionTraceData exd && exd.ExceptionType.Length == 0 )
return;
// --- Process filter ---
if( _processFilter != null && !data.ProcessName.Contains( _processFilter, StringComparison.OrdinalIgnoreCase ) )
{
return;
}
// --- Symbol Resolution ---
var callStack = data.CallStack();
if( callStack != null && _resolveNativeSymbols )
{
ResolveNativeStack( callStack );
}
// --- Pass to user ---
_userCallback( data );
}
/// <summary>
/// Resolves native symbols for a given call stack.
/// This method locks the SymbolReader, as it is not thread-safe.
/// </summary>
private void ResolveNativeStack( TraceCallStack callStack )
{
if( _symbolReader == null )
return;
lock( _symbolReaderLock )
{
while( callStack != null )
{
var codeAddress = callStack.CodeAddress;
if( codeAddress.Method == null )
{
var moduleFile = codeAddress.ModuleFile;
if( moduleFile != null )
{
codeAddress.CodeAddresses.LookupSymbolsForModule( _symbolReader, moduleFile );
}
}
callStack = callStack.Caller;
}
}
}
}
/// <summary>
/// Example implementation of the RealTimeTraceMonitor utility.
/// </summary>
internal class TraceLogMonitor
{
public static void Run()
{
log.info( "******************** RealTimeTraceLog DEMO ********************" );
// Define a thread-safe callback to process events
Action<TraceEvent> printCallback = ( TraceEvent data ) =>
{
// log.info is assumed to be thread-safe
// Note: EventPipe data does not have ProcessName, as it's scoped to the process.
log.info( $"EVENT: [PID:{data.ProcessID}] -- {data.EventName}" );
var stack = data.CallStack();
if( stack != null )
{
log.info( $"CALLSTACK: {stack}" );
}
};
// Run an exception generator in the background
Task.Factory.StartNew( delegate
{
Thread.Sleep( 3000 );
ThrowException();
} );
try
{
if( OperatingSystem.IsWindows() )
{
log.info( "Windows detected. Using ETW-based RealTimeTraceMonitor." );
using( var monitor = new RealTimeTraceMonitor() )
{
monitor.WithDuration( 10 )
.MonitorExceptions()
.MonitorModuleLoads()
.Start( printCallback );
}
}
else
{
log.info( "Non-Windows detected. Using EventPipe-based EventPipeTraceMonitor." );
int currentPid = Process.GetCurrentProcess().Id;
using( var monitor = new EventPipeTraceMonitor( currentPid ) )
{
monitor.WithDuration( 10 )
.MonitorExceptions()
.MonitorModuleLoads()
//.MonitorCpuSamples() // Note: EventPipe CPU sampling is very noisy
.Start( printCallback );
}
}
}
catch( Exception ex )
{
log.error( $"Monitoring failed: {ex}" );
}
log.info( "Finished monitoring." );
}
// --- Exception generation helpers ---
[System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.NoInlining )]
private static void ThrowException()
{
ThrowException1();
}
[System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.NoInlining )]
private static void ThrowException1()
{
log.info( "Causing an exception to happen so a CLR Exception Start event will be generated." );
try
{
throw new Exception( "This is a test exception thrown to generate a CLR event" );
}
catch( Exception ) { }
}
}
}

View File

@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Threading.Tasks;
using System.Diagnostics;
using System.Reflection;
namespace mod
{
[Serializable]
public class Config : lib.Config
{
public String name = "Generic";
}
public class View
{
}
public class Base
{
public Config Cfg { get { return m_cfg; } }
public Base( Config cfg )
{
m_cfg = cfg;
}
private Config m_cfg;
}
[Serializable]
public class FluidConfig : Config
{
public String type = "none";
}
public class FluidBase : Base
{
public new FluidConfig Cfg { get { return (FluidConfig)base.Cfg; } }
public FluidBase( FluidConfig cfg )
: base( cfg )
{
}
}
[Serializable]
public class SystemConfig : Config
{
public String type = "none";
}
public class System
{
public SystemConfig Cfg { get { return m_cfg; } }
public System( SystemConfig cfg )
{
m_cfg = cfg;
}
private SystemConfig m_cfg;
}
}

View File

@ -1,198 +0,0 @@
#nullable enable
using System;
using System.Runtime.Serialization;
using System.Net.Sockets;
using System.IO;
using System.Diagnostics.CodeAnalysis;
//using Util;
namespace lib
{
public interface IFormatter
{
//
// Summary:
// Gets or sets the System.Runtime.Serialization.SerializationBinder that performs
// type lookups during deserialization.
//
// Returns:
// The System.Runtime.Serialization.SerializationBinder that performs type lookups
// during deserialization.
SerializationBinder? Binder { get; set; }
//
// Summary:
// Gets or sets the System.Runtime.Serialization.StreamingContext used for serialization
// and deserialization.
//
// Returns:
// The System.Runtime.Serialization.StreamingContext used for serialization and
// deserialization.
StreamingContext Context { get; set; }
//
// Summary:
// Gets or sets the System.Runtime.Serialization.SurrogateSelector used by the current
// formatter.
//
// Returns:
// The System.Runtime.Serialization.SurrogateSelector used by this formatter.
ISurrogateSelector? SurrogateSelector { get; set; }
//
// Summary:
// Deserializes the data on the provided stream and reconstitutes the graph of objects.
//
//
// Parameters:
// serializationStream:
// The stream that contains the data to deserialize.
//
// Returns:
// The top object of the deserialized graph.
[RequiresDynamicCode( "BinaryFormatter serialization uses dynamic code generation, the type of objects being processed cannot be statically discovered." )]
[RequiresUnreferencedCode( "BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered." )]
object Deserialize( Stream serializationStream );
//
// Summary:
// Serializes an object, or graph of objects with the given root to the provided
// stream.
//
// Parameters:
// serializationStream:
// The stream where the formatter puts the serialized data. This stream can reference
// a variety of backing stores (such as files, network, memory, and so on).
//
// graph:
// The object, or root of the object graph, to serialize. All child objects of this
// root object are automatically serialized.
[RequiresUnreferencedCode( "BinaryFormatter serialization is not trim compatible because the type of objects being processed cannot be statically discovered." )]
void Serialize( Stream serializationStream, object graph );
}
public interface IProcess
{
void process( object obj );
}
public interface ISerDes<T> where T : IFormatter
{
T getInstance();
}
public class NewEveryCall<T> : ISerDes<T> where T : IFormatter, new()
{
public T getInstance()
{
return new T();
}
}
public class Conn
{
public static int BufferSize = 2048;
}
public class Conn<T, TInst> : Conn
where T : IFormatter, new()
where TInst : ISerDes<T>, new()
{
public Socket Sock { get { return m_socket; } }
public Stream Stream { get { return m_streamNet; } }
private TInst m_formatter = new TInst();
public Conn( Socket sock, IProcess proc )
{
m_socket = sock;
sock.NoDelay = true;
m_streamNet = new NetworkStream( m_socket );
m_proc = proc;
}
public object receiveObject()
{
return receiveObject( Stream );
}
public object receiveObject( Stream stream )
{
object obj = new object();
var formatter = m_formatter.getInstance();
try
{
obj = formatter.Deserialize( stream );
}
catch( System.Xml.XmlException ex )
{
log.error( $"Outer Exception {ex.Message}" );
}
return obj;
}
public void send( object obj )
{
var formatter = m_formatter.getInstance();
try
{
var ms = new MemoryStream( BufferSize );
formatter.Serialize( ms, obj );
//var str = System.Text.Encoding.Default.GetString( mm_buffer, 0, (int)ms.Position );
//log.info( $"Sent data {str} of length {ms.Position}" );
//log.info( $"Sent {obj}" );
byte[] byteSize = BitConverter.GetBytes( (uint)ms.Position );
m_streamNet.Write( byteSize, 0, 4 );
m_streamNet.Write( ms.GetBuffer(), 0, (int)ms.Position );
m_streamNet.Flush();
}
catch( Exception e )
{
log.warn( $"Exception sending obj {obj} of {e}" );
throw;
}
}
public virtual void receive( object obj )
{
if( m_proc != null )
m_proc.process( obj );
}
Socket m_socket;
NetworkStream m_streamNet;
IProcess m_proc;
//private BufferedStream m_streamBufIn;
//private BufferedStream m_streamBufOut;
}
}

View File

@ -1,66 +0,0 @@

using System;
namespace net;
public class Context
{
}
public class State<T, CTX>
where T : State<T, CTX>
where CTX : Context
{
virtual public void onEnter( CTX ctx, State<T, CTX> oldState )
{
}
virtual public void onExit( CTX ctx, State<T, CTX> newState )
{
}
}
/*
public class PotentialState<T, CTX> : State<T, CTX>
where T : State<T, CTX>
where CTX : Context
{
virtual public void onEnter(CTX ctx, State<T, CTX> oldState)
{
}
virtual public void onExit(CTX ctx, State<T, CTX> newState)
{
}
}
*/
public class FSM<T, CTX, ST>
where T : FSM<T, CTX, ST>
where CTX : Context
where ST : State<ST, CTX>
{
public CTX Context { get; private set; }
public ST State { get; private set; }
public FSM( CTX context, ST state )
{
Context = context;
State = state;
}
public void Transition( ST newState )
{
}
}

View File

@ -1,103 +0,0 @@
using System;
namespace lib.Net
{
[Serializable]
public class Msg
{
public Msg()
{
}
}
[Serializable]
public class Login
{
public Login( String name, String pass )
{
m_username = name;
m_password = pass;
}
public readonly String m_username;
public readonly String m_password;
}
[Serializable]
public class LoginResp
{
public LoginResp( bool resp )
{
m_resp = resp;
}
public readonly bool m_resp;
}
#region Admin Messages
//Subclasses of this need to be on an admin client.
[Serializable]
public class Admin
{
};
[Serializable]
public class CreateEntity : Admin
{
}
[Serializable]
public class MoveEntity : Admin
{
}
#endregion
[Serializable]
public class EntityBase
{
public EntityBase( int id )
{
m_id = id;
}
public readonly int m_id;
};
[Serializable]
public class EntityPos : EntityBase
{
public EntityPos( int id, float x, float y, float z ) :
base( id )
{
m_x = x;
m_y = y;
m_z = z;
}
public readonly float m_x;
public readonly float m_y;
public readonly float m_z;
}
[Serializable]
public class EntityDesc : EntityBase
{
public EntityDesc( int id ) :
base( id )
{
}
//Should an entity have a mesh? Be made up of multiple meshes?
public readonly String m_mesh;
}
}

View File

@ -1,47 +0,0 @@
using System.Collections.Generic;
namespace Tracing
{
public class AddressStack
{
// the first frame is the address of the last called method
private readonly List<ulong> _stack;
public AddressStack( int capacity )
{
_stack = new List<ulong>( capacity );
}
// No need to override GetHashCode because we don't want to use it as a key in a dictionary
public override bool Equals( object obj )
{
if( obj == null )
return false;
var stack = obj as AddressStack;
if( stack == null )
return false;
var frameCount = _stack.Count;
if( frameCount != stack._stack.Count )
return false;
for( int i = 0; i < frameCount; i++ )
{
if( _stack[i] != stack._stack[i] )
return false;
}
return true;
}
public override int GetHashCode() => _stack.GetHashCode();
public IReadOnlyList<ulong> Stack => _stack;
public void AddFrame( ulong address )
{
_stack.Add( address );
}
}
}

View File

@ -1,314 +0,0 @@
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
using ProfilerHelpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Tracing
{
public class Memory
{
private readonly TraceEventSession _session;
private readonly PerProcessProfilingState _processes;
// because we are not interested in self monitoring
private readonly int _currentPid;
private int _started = 0;
public Memory( TraceEventSession session, PerProcessProfilingState processes )
{
_session = session;
_processes = processes;
_currentPid = Process.GetCurrentProcess().Id;
}
public async Task StartAsync( bool allAllocations )
{
if( Interlocked.CompareExchange( ref _started, 1, 0 ) == 1 )
{
throw new InvalidOperationException( "Impossible to start profiling more than once." );
}
await Task.Factory.StartNew( () =>
{
using( _session )
{
log.info( $"SetupProviders" );
SetupProviders( _session, allAllocations );
log.info( $"SetupListeners" );
SetupListeners( _session.Source );
log.info( $"Source.Process()" );
while( _session.Source.Process() )
{
Task.Delay( 1 );
}
log.info( $"Done" );
}
} );
}
private void SetupProviders( TraceEventSession session, bool noSampling )
{
// Note: the kernel provider MUST be the first provider to be enabled
// If the kernel provider is not enabled, the callstacks for CLR events are still received
// but the symbols are not found (except for the application itself)
// Maybe a TraceEvent implementation details triggered when a module (image) is loaded
var success = true;
//*
log.info( $"EnableKernelProvider" );
success = log.var( session.EnableKernelProvider( KernelTraceEventParser.Keywords.None |
// KernelTraceEventParser.Keywords.ImageLoad |
// KernelTraceEventParser.Keywords.Process
0,
KernelTraceEventParser.Keywords.None
) );
log.info( $"EnableKernelProvider {success}" );
//*/
// The CLR source code indicates that the provider must be set before the monitored application starts
// Note: no real difference between High and Low
ClrTraceEventParser.Keywords eventsKeyword = noSampling
? ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow | ClrTraceEventParser.Keywords.GCSampledObjectAllocationHigh
: ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow
;
log.info( $"EnableProvider" );
success = log.var( session.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose, // this is needed in order to receive GCSampledObjectAllocation event
(ulong)(
eventsKeyword |
// required to receive the BulkType events that allows
// mapping between the type ID received in the allocation events
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
ClrTraceEventParser.Keywords.Type |
// events related to JITed methods
ClrTraceEventParser.Keywords.Jit | // Turning on JIT events is necessary to resolve JIT compiled code
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap | // This is needed if you want line number information in the stacks
ClrTraceEventParser.Keywords.Loader | // You must include loader events as well to resolve JIT compiled code.
// this is mandatory to get the callstacks in each CLR event payload.
//ClrTraceEventParser.Keywords.Stack |
0
)
) );
log.info( $"EnableProvider {success}" );
// Note: ClrRundown is not needed because only new processes will be monitored
}
private void SetupListeners( ETWTraceEventSource source )
{
// register for high and low keyword
// if both are set, each allocation will trigger an event (beware performance issues...)
source.Clr.GCSampledObjectAllocation += OnSampleObjectAllocation;
// required to receive the mapping between type ID (received in GCSampledObjectAllocation)
// and their name (received in TypeBulkType)
source.Clr.TypeBulkType += OnTypeBulkType;
// messages to get callstacks
// the correlation seems to be as "simple" as taking the last event on the same thread
source.Clr.ClrStackWalk += OnClrStackWalk;
// needed to get JITed method details
source.Clr.MethodLoadVerbose += OnMethodDetails;
source.Clr.MethodDCStartVerboseV2 += OnMethodDetails;
source.Clr.ContentionLockCreated += OnLockCreated;
source.Clr.ContentionStart += OnLockStart;
source.Clr.ContentionStop += OnLockStop;
// get notified when a module is load to map the corresponding symbols
source.Kernel.ImageLoad += OnImageLoad;
}
private void OnLockCreated( ContentionLockCreatedTraceData data )
{
}
private void OnLockStart( ContentionStartTraceData data )
{
}
private void OnLockStop( ContentionStopTraceData data )
{
}
private void OnImageLoad( ImageLoadTraceData data )
{
if( FilterOutEvent( data ) )
return;
GetProcessMethods( data.ProcessID ).AddModule( data.FileName, data.ImageBase, data.ImageSize );
log.info( $"{data.ProcessID}.{data.ThreadID} --> {data.FileName}" );
}
private void OnMethodDetails( MethodLoadUnloadVerboseTraceData data )
{
if( FilterOutEvent( data ) )
return;
// care only about jitted methods
if( !data.IsJitted )
return;
var method = GetProcessMethods( data.ProcessID )
.Add( data.MethodStartAddress, data.MethodSize, data.MethodNamespace, data.MethodName, data.MethodSignature );
log.info( $"0x{data.MethodStartAddress.ToString( "x12" )} - {data.MethodSize,6} | {data.MethodName}" );
}
private MethodStore GetProcessMethods( int pid )
{
if( !_processes.Methods.TryGetValue( pid, out var methods ) )
{
methods = new MethodStore( pid );
_processes.Methods[pid] = methods;
}
return methods;
}
private void OnSampleObjectAllocation( GCSampledObjectAllocationTraceData data )
{
if( FilterOutEvent( data ) )
return;
var typeName = GetProcessTypeName( data.ProcessID, data.TypeID );
if( data.TotalSizeForTypeSample >= 85000 )
{
var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | Alloc {GetProcessTypeName( data.ProcessID, data.TypeID )} ({data.TotalSizeForTypeSample})";
log.info( message );
}
GetProcessAllocations( data.ProcessID )
.AddAllocation(
data.ThreadID,
(ulong)data.TotalSizeForTypeSample,
(ulong)data.ObjectCountForTypeSample,
typeName
);
}
private ProcessAllocations GetProcessAllocations( int pid )
{
if( !_processes.Allocations.TryGetValue( pid, out var allocations ) )
{
allocations = new ProcessAllocations( pid );
_processes.Allocations[pid] = allocations;
}
return allocations;
}
private void OnClrStackWalk( ClrStackWalkTraceData data )
{
if( FilterOutEvent( data ) )
return;
//var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | {data.FrameCount} frames";
//log.info(message);
var callstack = BuildCallStack( data );
GetProcessAllocations( data.ProcessID ).AddStack( data.ThreadID, callstack );
//DumpStack(data);
}
private AddressStack BuildCallStack( ClrStackWalkTraceData data )
{
var length = data.FrameCount;
AddressStack stack = new AddressStack( length );
// frame 0 is the last frame of the stack (i.e. last called method)
for( int i = 0; i < length; i++ )
{
stack.AddFrame( data.InstructionPointer( i ) );
}
return stack;
}
private void DumpStack( ClrStackWalkTraceData data )
{
var methods = GetProcessMethods( data.ProcessID );
for( int i = 0; i < data.FrameCount; i++ )
{
var address = data.InstructionPointer( i );
log.info( methods.GetFullName( address ) );
}
log.info( $"" );
}
private void OnTypeBulkType( GCBulkTypeTraceData data )
{
if( FilterOutEvent( data ) )
return;
ProcessTypeMapping mapping = GetProcessTypesMapping( data.ProcessID );
for( int currentType = 0; currentType < data.Count; currentType++ )
{
GCBulkTypeValues value = data.Values( currentType );
mapping[value.TypeID] = value.TypeName;
}
}
private ProcessTypeMapping GetProcessTypesMapping( int pid )
{
ProcessTypeMapping mapping;
if( !_processes.Types.TryGetValue( pid, out mapping ) )
{
AssociateProcess( pid );
mapping = new ProcessTypeMapping( pid );
_processes.Types[pid] = mapping;
}
return mapping;
}
private void AssociateProcess( int pid )
{
try
{
_processes.Names[pid] = Process.GetProcessById( pid ).ProcessName;
}
catch( Exception )
{
log.info( $"? {pid}" );
// we might not have access to the process
}
}
private string GetProcessTypeName( int pid, ulong typeID )
{
if( !_processes.Types.TryGetValue( pid, out var mapping ) )
{
return typeID.ToString();
}
var name = mapping[typeID];
return string.IsNullOrEmpty( name ) ? typeID.ToString() : name;
}
private bool FilterOutEvent( TraceEvent data )
{
return ( data.ProcessID == _currentPid );
}
}
}

View File

@ -1,393 +0,0 @@
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
using ProfilerHelpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Threading;
using System.Threading.Tasks;
using TraceKeywords = Microsoft.Diagnostics.Tracing.Parsers.ClrTraceEventParser.Keywords;
namespace Tracing
{
public class MemoryPipe
{
private readonly DiagnosticsClient _client;
private readonly PerProcessProfilingState _processes;
// because we are not interested in self monitoring
private readonly int _currentPid;
private int _started = 0;
Thread _thread;
public MemoryPipe()
{
_currentPid = Process.GetCurrentProcess().Id;
_client = new DiagnosticsClient( _currentPid );
_processes = new();
var threadStart = new ThreadStart( () => Start( true ) );
_thread = new Thread( threadStart );
_thread.Start();
}
//public async Task StartAsync( bool allAllocations )
public void Start( bool allAllocations )
{
if( Interlocked.CompareExchange( ref _started, 1, 0 ) == 1 )
{
throw new InvalidOperationException( "Impossible to start profiling more than once." );
}
log.info( $"Starting MemoryPipe on thread {Thread.CurrentThread.ManagedThreadId}" );
var providers = new List<EventPipeProvider>()
{
new EventPipeProvider("Microsoft-Windows-DotNETRuntime",
EventLevel.Verbose, (long)(
TraceKeywords.GC |
TraceKeywords.Contention |
TraceKeywords.Debugger |
TraceKeywords.Exception |
TraceKeywords.GCAllObjectAllocation |
TraceKeywords.GCSampledObjectAllocationHigh |
TraceKeywords.GCSampledObjectAllocationLow |
TraceKeywords.Security |
TraceKeywords.Threading |
TraceKeywords.Type |
TraceKeywords.TypeDiagnostic |
TraceKeywords.WaitHandle |
TraceKeywords.All
) )
};
//await Task.Factory.StartNew( () => {
using EventPipeSession session = _client.StartEventPipeSession( providers, false );
//log.info( $"SetupProviders" );
//SetupProviders( session, allAllocations );
var source = new EventPipeEventSource( session.EventStream );
log.info( $"SetupListeners" );
SetupListeners( source );
log.info( $"Run Process" );
source.Process();
log.info( $"Done" );
//} );
}
private void SetupProviders( TraceEventSession session, bool noSampling )
{
// Note: the kernel provider MUST be the first provider to be enabled
// If the kernel provider is not enabled, the callstacks for CLR events are still received
// but the symbols are not found (except for the application itself)
// Maybe a TraceEvent implementation details triggered when a module (image) is loaded
var success = true;
//*
log.info( $"EnableKernelProvider" );
success = log.var( session.EnableKernelProvider( KernelTraceEventParser.Keywords.None |
// KernelTraceEventParser.Keywords.ImageLoad |
// KernelTraceEventParser.Keywords.Process
0,
KernelTraceEventParser.Keywords.None
) );
log.info( $"EnableKernelProvider {success}" );
//*/
// The CLR source code indicates that the provider must be set before the monitored application starts
// Note: no real difference between High and Low
ClrTraceEventParser.Keywords eventsKeyword = noSampling
? ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow | ClrTraceEventParser.Keywords.GCSampledObjectAllocationHigh
: ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow
;
log.info( $"EnableProvider" );
success = log.var( session.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose, // this is needed in order to receive GCSampledObjectAllocation event
(ulong)(
eventsKeyword |
// required to receive the BulkType events that allows
// mapping between the type ID received in the allocation events
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
ClrTraceEventParser.Keywords.Type |
// events related to JITed methods
ClrTraceEventParser.Keywords.Jit | // Turning on JIT events is necessary to resolve JIT compiled code
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap | // This is needed if you want line number information in the stacks
ClrTraceEventParser.Keywords.Loader | // You must include loader events as well to resolve JIT compiled code.
// this is mandatory to get the callstacks in each CLR event payload.
//ClrTraceEventParser.Keywords.Stack |
0
)
) );
log.info( $"EnableProvider {success}" );
// Note: ClrRundown is not needed because only new processes will be monitored
}
private void SetupListeners( EventPipeEventSource source )
{
// register for high and low keyword
// if both are set, each allocation will trigger an event (beware performance issues...)
//source.Clr.GCSampledObjectAllocation += OnSampleObjectAllocation;
source.Clr.GCAllocationTick += OnAllocTick;
// required to receive the mapping between type ID (received in GCSampledObjectAllocation)
// and their name (received in TypeBulkType)
source.Clr.TypeBulkType += OnTypeBulkType;
// messages to get callstacks
// the correlation seems to be as "simple" as taking the last event on the same thread
source.Clr.ClrStackWalk += OnClrStackWalk;
// needed to get JITed method details
source.Clr.MethodLoadVerbose += OnMethodDetails;
source.Clr.MethodDCStartVerboseV2 += OnMethodDetails;
source.Clr.ContentionLockCreated += OnLockCreated;
source.Clr.ContentionStart += OnLockStart;
source.Clr.ContentionStop += OnLockStop;
// get notified when a module is load to map the corresponding symbols
source.Kernel.ImageLoad += OnImageLoad;
}
private void OnAllocTick( GCAllocationTickTraceData data )
{
if( FilterOutEvent( data ) )
return;
//log.info( $"*** RAW: {data}" );
//var callStack = data.CallStack();
//var caller = callStack.Caller;
//log.info( $"Call stack {callStack}" );
var thisThreadId = Thread.CurrentThread.ManagedThreadId;
var typeName = GetProcessTypeName( data.ProcessID, data.TypeID );
//if( data.TotalSizeForTypeSample >= 85000 )
{
var message = $"{data.ThreadID}/{thisThreadId} Alloc {data.ObjectSize,8} at 0x{data.Address:0000000000000000} in {data.TypeName} (or {GetProcessTypeName( data.ProcessID, data.TypeID )}) ";
log.info( message );
}
GetProcessAllocations( data.ProcessID )
.AddAllocation(
data.ThreadID,
(ulong)data.ObjectSize,
(ulong)1,
typeName
);
}
private void OnLockCreated( ContentionLockCreatedTraceData data )
{
log.info( $"{data}" );
}
private void OnLockStart( ContentionStartTraceData data )
{
log.info( $"{data}" );
}
private void OnLockStop( ContentionStopTraceData data )
{
log.info( $"{data}" );
}
private void OnImageLoad( ImageLoadTraceData data )
{
//if( FilterOutEvent( data ) )
// return;
log.info( $"{data}" );
//GetProcessMethods( data.ProcessID ).AddModule( data.FileName, data.ImageBase, data.ImageSize );
log.info( $"{data.ProcessID}.{data.ThreadID} --> {data.FileName}" );
}
private void OnMethodDetails( MethodLoadUnloadVerboseTraceData data )
{
//if( FilterOutEvent( data ) )
// return;
//log.info( $"{data}" );
// care only about jitted methods
if( !data.IsJitted )
return;
var method = GetProcessMethods( data.ProcessID )
.Add( data.MethodStartAddress, data.MethodSize, data.MethodNamespace, data.MethodName, data.MethodSignature );
//log.info( $"0x{data.MethodStartAddress.ToString( "x12" )} - {data.MethodSize,6} | {data.MethodName}" );
}
private MethodStore GetProcessMethods( int pid )
{
if( !_processes.Methods.TryGetValue( pid, out var methods ) )
{
methods = new MethodStore( pid );
_processes.Methods[pid] = methods;
}
return methods;
}
private void OnSampleObjectAllocation( GCSampledObjectAllocationTraceData data )
{
if( FilterOutEvent( data ) )
return;
//log.info( $"{data}" );
var typeName = GetProcessTypeName( data.ProcessID, data.TypeID );
//if( data.TotalSizeForTypeSample >= 85000 )
{
var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | Alloc {GetProcessTypeName( data.ProcessID, data.TypeID )} ({data.TotalSizeForTypeSample})";
log.info( message );
}
GetProcessAllocations( data.ProcessID )
.AddAllocation(
data.ThreadID,
(ulong)data.TotalSizeForTypeSample,
(ulong)data.ObjectCountForTypeSample,
typeName
);
}
private ProcessAllocations GetProcessAllocations( int pid )
{
if( !_processes.Allocations.TryGetValue( pid, out var allocations ) )
{
allocations = new ProcessAllocations( pid );
_processes.Allocations[pid] = allocations;
}
return allocations;
}
private void OnClrStackWalk( ClrStackWalkTraceData data )
{
var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | {data.FrameCount} frames";
log.info( message );
var callstack = BuildCallStack( data );
GetProcessAllocations( data.ProcessID ).AddStack( data.ThreadID, callstack );
//DumpStack(data);
}
private AddressStack BuildCallStack( ClrStackWalkTraceData data )
{
//log.info( $"{data}" );
var length = data.FrameCount;
AddressStack stack = new AddressStack( length );
// frame 0 is the last frame of the stack (i.e. last called method)
for( int i = 0; i < length; i++ )
{
stack.AddFrame( data.InstructionPointer( i ) );
}
return stack;
}
private void DumpStack( ClrStackWalkTraceData data )
{
if( FilterOutEvent( data ) )
return;
log.info( $"{data}" );
var methods = GetProcessMethods( data.ProcessID );
for( int i = 0; i < data.FrameCount; i++ )
{
var address = data.InstructionPointer( i );
log.info( methods.GetFullName( address ) );
}
log.info( $"" );
}
private void OnTypeBulkType( GCBulkTypeTraceData data )
{
ProcessTypeMapping mapping = GetProcessTypesMapping( data.ProcessID );
for( int currentType = 0; currentType < data.Count; currentType++ )
{
GCBulkTypeValues value = data.Values( currentType );
mapping[value.TypeID] = value.TypeName;
//log.info( $"{value}" );
}
}
private ProcessTypeMapping GetProcessTypesMapping( int pid )
{
ProcessTypeMapping mapping;
if( !_processes.Types.TryGetValue( pid, out mapping ) )
{
AssociateProcess( pid );
mapping = new ProcessTypeMapping( pid );
_processes.Types[pid] = mapping;
}
return mapping;
}
private void AssociateProcess( int pid )
{
try
{
_processes.Names[pid] = Process.GetProcessById( pid ).ProcessName;
}
catch( Exception )
{
log.info( $"? {pid}" );
// we might not have access to the process
}
}
private string GetProcessTypeName( int pid, ulong typeID )
{
if( !_processes.Types.TryGetValue( pid, out var mapping ) )
{
return typeID.ToString();
}
var name = mapping[typeID];
return string.IsNullOrEmpty( name ) ? typeID.ToString() : name;
}
private bool FilterOutEvent( TraceEvent data )
{
return data.ThreadID == Thread.CurrentThread.ManagedThreadId;
//return false; // ( data.ProcessID == _currentPid );
}
}
}

View File

@ -1,68 +0,0 @@
using System;
namespace ProfilerHelpers
{
public class MethodInfo
{
private readonly ulong _startAddress;
private readonly int _size;
private readonly string _fullName;
internal MethodInfo( ulong startAddress, int size, string namespaceAndTypeName, string name, string signature )
{
_startAddress = startAddress;
_size = size;
_fullName = ComputeFullName( startAddress, namespaceAndTypeName, name, signature );
}
private string ComputeFullName( ulong startAddress, string namespaceAndTypeName, string name, string signature )
{
var fullName = signature;
// constructor case: name = .ctor | namespaceAndTypeName = A.B.typeName | signature = ... (parameters)
// --> A.B.typeName(parameters)
if( name == ".ctor" )
{
return $"{namespaceAndTypeName}{ExtractParameters( signature )}";
}
// general case: name = Foo | namespaceAndTypeName = A.B.typeName | signature = ... (parameters)
// --> A.B.Foo(parameters)
fullName = $"{namespaceAndTypeName}.{name}{ExtractParameters( signature )}";
return fullName;
}
private string ExtractTypeName( string namespaceAndTypeName )
{
var pos = namespaceAndTypeName.LastIndexOf( ".", StringComparison.Ordinal );
if( pos == -1 )
{
return namespaceAndTypeName;
}
// skip the .
pos++;
return namespaceAndTypeName.Substring( pos );
}
private string ExtractParameters( string signature )
{
var pos = signature.IndexOf( " (" );
if( pos == -1 )
{
return "(???)";
}
// skip double space
pos += 2;
var parameters = signature.Substring( pos );
return parameters;
}
public ulong StartAddress => _startAddress;
public int Size => _size;
public string FullName => _fullName;
}
}

View File

@ -1,209 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace ProfilerHelpers
{
public class MethodStore : IDisposable
{
// JITed methods information (start address + size + signature)
private readonly List<MethodInfo> _methods;
// addresses from callstacks already matching (address -> full name)
private readonly Dictionary<ulong, string> _cache;
// for native methods, rely on dbghelp API
// so the process handle is required
private IntPtr _hProcess;
private Process _process;
private readonly int _pid;
public MethodStore( int pid, bool loadModules = false )
{
// it may be possible to open the process
// in that case, _hProcess = IntPtr.Zero
_pid = pid;
_methods = new List<MethodInfo>( 1024 );
_cache = new Dictionary<ulong, string>();
_hProcess = BindToProcess( pid, loadModules );
}
private IntPtr BindToProcess( int pid, bool loadModules )
{
try
{
_process = Process.GetProcessById( pid );
if( !SymInitialize( _process.Handle, loadModules ) )
return IntPtr.Zero;
return _process.Handle;
}
catch( Exception x )
{
Console.WriteLine( $"Error while binding pid #{pid} to DbgHelp:" );
Console.WriteLine( x.Message );
return IntPtr.Zero;
}
}
private bool SymInitialize( IntPtr hProcess, bool loadModules = false )
{
// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions for more details
// maybe SYMOPT_NO_PROMPTS and SYMOPT_FAIL_CRITICAL_ERRORS could be used
NativeDbgHelp.SymSetOptions(
NativeDbgHelp.SYMOPT_DEFERRED_LOADS | // performance optimization
NativeDbgHelp.SYMOPT_UNDNAME // C++ names are not mangled
);
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
// search path for symbols:
// - The current working directory of the application
// - The _NT_SYMBOL_PATH environment variable
// - The _NT_ALTERNATE_SYMBOL_PATH environment variable
//
// passing false as last parameter means that we will need to call SymLoadModule64
// each time a module is loaded in the process
return NativeDbgHelp.SymInitialize( hProcess, null, loadModules );
}
public MethodInfo Add( ulong address, int size, string namespaceAndTypeName, string name, string signature )
{
var method = new MethodInfo( address, size, namespaceAndTypeName, name, signature );
_methods.Add( method );
return method;
}
public string GetFullName( ulong address )
{
if( _cache.TryGetValue( address, out var fullName ) )
return fullName;
// look for managed methods
for( int i = 0; i < _methods.Count; i++ )
{
var method = _methods[i];
if( ( address >= method.StartAddress ) && ( address < method.StartAddress + (ulong)method.Size ) )
{
fullName = method.FullName;
_cache[address] = fullName;
return fullName;
}
}
// look for native methods
fullName = GetNativeMethodName( address );
_cache[address] = fullName;
return fullName;
}
private string GetNativeMethodName( ulong address )
{
var symbol = new NativeDbgHelp.SYMBOL_INFO();
symbol.MaxNameLen = 1024;
symbol.SizeOfStruct = (uint)Marshal.SizeOf( symbol ) - 1024; // char buffer is not counted
// the ANSI version of SymFromAddr is called so each character is 1 byte long
if( NativeDbgHelp.SymFromAddr( _hProcess, address, out var displacement, ref symbol ) )
{
var buffer = new StringBuilder( symbol.Name.Length );
// remove weird "$##" at the end of some symbols
var pos = symbol.Name.LastIndexOf( "$##" );
if( pos == -1 )
buffer.Append( symbol.Name );
else
buffer.Append( symbol.Name, 0, pos );
// add offset if any
if( displacement != 0 )
buffer.Append( $"+0x{displacement}" );
return buffer.ToString();
}
// default value is just the address in HEX
#if DEBUG
return ( $"0x{address:x} (SymFromAddr failed with 0x{Marshal.GetLastWin32Error():x})" );
#else
return $"0x{address:x}";
#endif
}
const int ERROR_SUCCESS = 0;
public void AddModule( string filename, ulong baseOfDll, int sizeOfDll )
{
var baseAddress = NativeDbgHelp.SymLoadModule64( _hProcess, IntPtr.Zero, filename, null, baseOfDll, (uint)sizeOfDll );
if( baseAddress == 0 )
{
// should work if the same module is added more than once
if( Marshal.GetLastWin32Error() == ERROR_SUCCESS )
return;
Console.WriteLine( $"SymLoadModule64 failed for {filename}" );
}
}
public void Dispose()
{
if( _hProcess == IntPtr.Zero )
return;
_hProcess = IntPtr.Zero;
_process.Dispose();
}
}
internal static class NativeDbgHelp
{
// from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc\dbghelp.h
public const uint SYMOPT_UNDNAME = 0x00000002;
public const uint SYMOPT_DEFERRED_LOADS = 0x00000004;
[StructLayout( LayoutKind.Sequential )]
public struct SYMBOL_INFO
{
public uint SizeOfStruct;
public uint TypeIndex; // Type Index of symbol
private ulong Reserved1;
private ulong Reserved2;
public uint Index;
public uint Size;
public ulong ModBase; // Base Address of module containing this symbol
public uint Flags;
public ulong Value; // Value of symbol, ValuePresent should be 1
public ulong Address; // Address of symbol including base address of module
public uint Register; // register holding value or pointer to value
public uint Scope; // scope of the symbol
public uint Tag; // pdb classification
public uint NameLen; // Actual length of name
public uint MaxNameLen;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 1024 )]
public string Name;
}
[DllImport( "dbghelp.dll", SetLastError = true )]
public static extern bool SymInitialize( IntPtr hProcess, string userSearchPath, bool invadeProcess );
[DllImport( "dbghelp.dll", SetLastError = true )]
public static extern uint SymSetOptions( uint symOptions );
[DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )]
public static extern ulong SymLoadModule64( IntPtr hProcess, IntPtr hFile, string imageName, string moduleName, ulong baseOfDll, uint sizeOfDll );
// use ANSI version to ensure the right size of the structure
// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info
[DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )]
public static extern bool SymFromAddr( IntPtr hProcess, ulong address, out ulong displacement, ref SYMBOL_INFO symbol );
[DllImport( "dbghelp.dll", SetLastError = true )]
public static extern bool SymCleanup( IntPtr hProcess );
}
}

View File

@ -1,33 +0,0 @@
using ProfilerHelpers;
using System;
using System.Collections.Generic;
namespace Tracing
{
public class PerProcessProfilingState : IDisposable
{
private bool _disposed;
private readonly Dictionary<int, string> _processNames = new Dictionary<int, string>();
private readonly Dictionary<int, ProcessTypeMapping> _perProcessTypes = new Dictionary<int, ProcessTypeMapping>();
private readonly Dictionary<int, ProcessAllocations> _perProcessAllocations = new Dictionary<int, ProcessAllocations>();
private readonly Dictionary<int, MethodStore> _methods = new Dictionary<int, MethodStore>();
public Dictionary<int, string> Names => _processNames;
public Dictionary<int, ProcessTypeMapping> Types => _perProcessTypes;
public Dictionary<int, ProcessAllocations> Allocations => _perProcessAllocations;
public Dictionary<int, MethodStore> Methods => _methods;
public void Dispose()
{
if( _disposed )
return;
_disposed = true;
foreach( var methodStore in _methods.Values )
{
methodStore.Dispose();
}
}
}
}

View File

@ -1,131 +0,0 @@
using System;
using System.Collections.Generic;
namespace Tracing
{
public class ProcessAllocations
{
private readonly int _pid;
private readonly Dictionary<string, AllocationInfo> _allocations;
private readonly Dictionary<int, AllocationInfo> _perThreadLastAllocation;
public ProcessAllocations( int pid )
{
_pid = pid;
_allocations = new Dictionary<string, AllocationInfo>();
_perThreadLastAllocation = new Dictionary<int, AllocationInfo>();
}
public int Pid => _pid;
public AllocationInfo GetAllocations( string typeName )
{
return ( _allocations.TryGetValue( typeName, out var info ) ) ? info : null;
}
public IEnumerable<AllocationInfo> GetAllAllocations()
{
return _allocations.Values;
}
public AllocationInfo AddAllocation( int threadID, ulong size, ulong count, string typeName )
{
if( !_allocations.TryGetValue( typeName, out var info ) )
{
info = new AllocationInfo( typeName );
_allocations[typeName] = info;
}
info.AddAllocation( size, count );
// the last allocation is still here without the corresponding stack
if( _perThreadLastAllocation.TryGetValue( threadID, out var lastAlloc ) )
{
Console.WriteLine( "no stack for the last allocation" );
}
// keep track of the allocation for the given thread
// --> will be used when the corresponding call stack event will be received
_perThreadLastAllocation[threadID] = info;
return info;
}
public void AddStack( int threadID, AddressStack stack )
{
if( _perThreadLastAllocation.TryGetValue( threadID, out var lastAlloc ) )
{
lastAlloc.AddStack( stack );
_perThreadLastAllocation.Remove( threadID );
return;
}
//Console.WriteLine("no last allocation for the stack event");
}
}
public class AllocationInfo
{
private readonly string _typeName;
private ulong _size;
private ulong _count;
private List<StackInfo> _stacks;
internal AllocationInfo( string typeName )
{
_typeName = typeName;
_stacks = new List<StackInfo>();
}
public string TypeName => _typeName;
public ulong Count => _count;
public ulong Size => _size;
public IReadOnlyList<StackInfo> Stacks => _stacks;
internal void AddAllocation( ulong size, ulong count )
{
_count += count;
_size += size;
}
internal void AddStack( AddressStack stack )
{
var info = GetInfo( stack );
if( info == null )
{
info = new StackInfo( stack );
_stacks.Add( info );
}
info.Count++;
}
private StackInfo GetInfo( AddressStack stack )
{
for( int i = 0; i < _stacks.Count; i++ )
{
var info = _stacks[i];
if( stack.Equals( info.Stack ) )
return info;
}
return null;
}
}
public class StackInfo
{
private readonly AddressStack _stack;
public ulong Count;
internal StackInfo( AddressStack stack )
{
Count = 0;
_stack = stack;
}
public AddressStack Stack => _stack;
}
}

View File

@ -1,35 +0,0 @@
using System.Collections.Generic;
namespace Tracing
{
// Contains the mapping between type ID received by SampleObjectAllocation(Low/High) events
// and their name received by TypeBulkType events
public class ProcessTypeMapping
{
private readonly Dictionary<ulong, string> _typesIdToName;
public ProcessTypeMapping( int processId )
{
ProcessId = processId;
_typesIdToName = new Dictionary<ulong, string>();
}
public int ProcessId { get; set; }
public string this[ulong id]
{
get
{
if( !_typesIdToName.ContainsKey( id ) )
return null;
return _typesIdToName[id];
}
set
{
_typesIdToName[id] = value;
}
}
}
}

View File

@ -1,162 +0,0 @@
using Microsoft.Diagnostics.Tracing.Session;
using ProfilerHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Tracing
{
static class Program
{
static public int CreateTracingSession( bool noSampling, bool sortBySize, int topTypesLimit )
{
ShowHeader();
try
{
TraceEventSession session = new TraceEventSession(
"Tracing",
TraceEventSessionOptions.Create
);
log.info( $"Create PerProcessProfilingState" );
using( var processes = new PerProcessProfilingState() )
{
log.info( $"Create Memory profiler for session" );
var profiler = new Memory( session, processes );
log.info( $"Start task" );
var task = profiler.StartAsync( noSampling );
/*
log.info( $"await the Continue" );
await task.ContinueWith( ( t ) => {
log.info( $"Task is done, Dispose" );
session.Dispose();
} );
*/
//log.info("Press ENTER to stop memory profiling");
//Console.ReadLine();
/*
try
{
await task;
ShowResults( processes, sortBySize, topTypesLimit );
return 0;
}
catch( Exception x )
{
log.info( x.Message );
ShowHelp();
}
*/
}
return -1;
}
catch( Exception x )
{
log.info( x.Message );
ShowHelp();
}
return -2;
}
private static void ShowResults( PerProcessProfilingState processes, bool sortBySize, int topTypesLimit )
{
foreach( var pid in processes.Allocations.Keys )
{
// skip processes without symbol resolution
if( !processes.Methods.ContainsKey( pid ) )
continue;
// skip processes without allocations
if( !processes.Allocations[pid].GetAllAllocations().Any() )
continue;
ShowResults( GetProcessName( pid, processes.Names ), processes.Methods[pid], processes.Allocations[pid], sortBySize, topTypesLimit );
}
}
private static string GetProcessName( int pid, Dictionary<int, string> names )
{
if( names.TryGetValue( pid, out var name ) )
return name;
return pid.ToString();
}
private static void ShowResults( string name, MethodStore methods, ProcessAllocations allocations, bool sortBySize, int topTypesLimit )
{
log.info( $"Memory allocations for {name}" );
log.info( $"" );
log.info( "---------------------------------------------------------" );
log.info( " Count Size Type" );
log.info( "---------------------------------------------------------" );
IEnumerable<AllocationInfo> types = ( sortBySize )
? allocations.GetAllAllocations().OrderByDescending( a => a.Size )
: allocations.GetAllAllocations().OrderByDescending( a => a.Count )
;
if( topTypesLimit != -1 )
types = types.Take( topTypesLimit );
foreach( var allocation in types )
{
log.info( $"{allocation.Count,9} {allocation.Size,11} {allocation.TypeName}" );
log.info( $"" );
DumpStacks( allocation, methods );
log.info( $"" );
}
log.info( $"" );
log.info( $"" );
}
private static void DumpStacks( AllocationInfo allocation, MethodStore methods )
{
var stacks = allocation.Stacks.OrderByDescending( s => s.Count ).Take( 10 );
foreach( var stack in stacks )
{
log.info( $"{stack.Count,6} allocations" );
log.info( "----------------------------------" );
DumpStack( stack.Stack, methods );
log.info( $"" );
}
}
private static void DumpStack( AddressStack stack, MethodStore methods )
{
var callstack = stack.Stack;
for( int i = 0; i < Math.Min( 10, callstack.Count ); i++ )
{
log.info( $" {methods.GetFullName( callstack[i] )}" );
}
}
private static void ShowHeader()
{
log.info( "Tracing v1.0.0 - Sampled memory profiler for .NET applications" );
log.info( "by Christophe Nasarre" );
log.info( $"" );
}
private static void ShowHelp()
{
log.info( $"" );
log.info( "Tracing shows sampled allocations of a given .NET application." );
log.info( "Usage: Tracing [-a (all allocations)] [-c (sort by count instead of default by size)] [-t <type count (instead of 3 types by default)>]" );
log.info( " Ex: Tracing -t -1 (all types sampled allocations sorted by size)" );
log.info( " Ex: Tracing -c -t 10 (allocations for top 10 types sorted by count)" );
log.info( $"" );
}
}
}

View File

@ -1,838 +0,0 @@
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

View File

@ -1,159 +0,0 @@
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);
}
}

View File

@ -1,188 +0,0 @@
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;
}
}

View File

@ -1,133 +0,0 @@
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" );
}
}

View File

@ -1,201 +0,0 @@
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();
}
}

View File

@ -1,375 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
namespace srl;
// --- INTERFACES ---
public interface Driver
{
// Structural
void BeginScope( string name, Type type, int id );
void EndScope();
void BeginCollection( string name, int count );
void EndCollection();
// Data
void WriteAttr( string name, string value ); // Primitives & Compact Strings
void WriteRef( string name, int id ); // DAG/Cycle Reference
// Metadata Hook
void OnProp( MemberInfo member, string name );
}
public interface IInput
{
string? Value { get; }
bool IsLeaf { get; }
IInput? GetAttr( string name );
IInput? GetChild( string name );
}
// --- THE MODEL (Introspection) ---
public class TypePlan
{
public string Name;
public bool IsCollection;
public bool IsHybrid; // True if Type has a StringParser registered
public StringParser Parser; // The "Compact" converter
public List<PropPlan> Props = new();
}
public class PropPlan
{
public string Name;
public string MemberName;
public Type Type;
public bool IsSmart; // Primitive/Enum/String
public MemberInfo Info;
// Fast Accessors
public Func<object, object?> Getter;
public Action<object, object?> Setter;
}
public static class Model
{
private static Dictionary<Type, TypePlan> _cache = new();
public static TypePlan Get( Type t )
{
if( _cache.TryGetValue( t, out var plan ) )
return plan;
plan = new TypePlan { Name = t.Name };
// 1. Check for Custom Parsers (Hybrid Mode)
if( Parsers.TryGet( t, out var parser ) )
{
plan.IsHybrid = true;
plan.Parser = parser;
}
// 2. Check for Collections
if( t != typeof( string ) && typeof( IEnumerable ).IsAssignableFrom( t ) )
{
plan.IsCollection = true;
_cache[t] = plan;
return plan;
}
// 3. Scan Properties
foreach( var p in t.GetProperties( BindingFlags.Public | BindingFlags.Instance ) )
{
if( Attribute.IsDefined( p, typeof( IgnoreAttribute ) ) )
continue;
var prop = new PropPlan
{
Name = p.Name, // Default to PascalCase, Drivers can lower if needed
MemberName = p.Name,
Type = p.PropertyType,
Info = p,
Getter = ( o ) => p.GetValue( o ),
Setter = ( o, v ) => p.SetValue( o, v )
};
// Override name
var nameAttr = p.GetCustomAttribute<NameAttribute>();
if( nameAttr != null )
prop.Name = nameAttr.Name;
// Is "Smart" (Atomic)?
prop.IsSmart = prop.Type.IsPrimitive || prop.Type.IsEnum ||
prop.Type == typeof( string ) || prop.Type == typeof( Guid );
plan.Props.Add( prop );
}
_cache[t] = plan;
return plan;
}
}
// --- ATTRIBUTES ---
public class IgnoreAttribute : Attribute { }
public class NameAttribute : Attribute { public string Name; public NameAttribute( string n ) => Name = n; }
// --- UTILITIES ---
public struct StringParser
{
public Func<object, string> To;
public Func<string, object> From;
}
public static class Parsers
{
private static Dictionary<Type, StringParser> _registry = new();
public static void Register<T>( Func<T, string> to, Func<string, T> from )
{
_registry[typeof( T )] = new StringParser { To = o => to( (T)o ), From = s => from( s ) };
}
public static bool TryGet( Type t, out StringParser p )
{
if( _registry.TryGetValue( t, out p ) )
return true;
// Auto-Discovery could go here (Static Parse methods)
return false;
}
}
public static class Binder
{
// Handles "Cool.Rare" dot notation
public static void Apply( object root, string path, string value )
{
var current = root;
var parts = path.Split( '.' );
for( int i = 0; i < parts.Length; i++ )
{
var part = parts[i];
var isLast = i == parts.Length - 1;
var plan = Model.Get( current.GetType() );
// Case-Insensitive Match
var prop = plan.Props.Find( p => p.Name.Equals( part, StringComparison.OrdinalIgnoreCase ) );
if( prop == null )
return;
if( isLast )
{
var val = ParseUtils.Convert( value, prop.Type );
prop.Setter( current, val );
}
else
{
var next = prop.Getter( current );
if( next == null )
{
next = Activator.CreateInstance( prop.Type );
prop.Setter( current, next );
}
current = next;
}
}
}
}
public static class ParseUtils
{
public static object Convert( string raw, Type t )
{
if( t == typeof( string ) )
return raw;
if( t == typeof( int ) )
return int.Parse( raw );
if( t == typeof( float ) )
return float.Parse( raw.Replace( "f", "" ) );
if( t == typeof( bool ) )
return bool.Parse( raw );
if( t.IsEnum )
return Enum.Parse( t, raw );
// Fallback to TypeConverter
var cv = TypeDescriptor.GetConverter( t );
if( cv != null && cv.CanConvertFrom( typeof( string ) ) )
return cv.ConvertFrom( raw );
return null;
}
public static void CopyFields( object src, object dst )
{
foreach( var p in src.GetType().GetProperties() )
if( p.CanRead && p.CanWrite )
p.SetValue( dst, p.GetValue( src ) );
}
}
// --- THE ENGINE (Walker & Loader) ---
public static class Walker
{
public class Context
{
private Dictionary<object, int> _seen = new( ReferenceEqualityComparer.Instance );
private int _nextId = 1;
public (int, bool) GetId( object o )
{
if( _seen.TryGetValue( o, out var id ) )
return (id, false);
_seen[o] = _nextId;
return (_nextId++, true);
}
}
public static void Serialize( object root, Driver d )
{
if( root == null )
return;
SerializeRecursive( root, d, new Context(), "root", null );
}
private static void SerializeRecursive( object obj, Driver d, Context ctx, string name, MemberInfo? member )
{
if( obj == null )
return;
Type type = obj.GetType();
var plan = Model.Get( type );
// STRATEGY 1: COMPACT (Hybrid Parser)
if( plan.IsHybrid )
{
if( member != null )
d.OnProp( member, name );
d.WriteAttr( name, plan.Parser.To( obj ) );
return;
}
// STRATEGY 2: DAG CHECK
bool isRef = !type.IsValueType && type != typeof( string );
if( isRef )
{
var (id, isNew) = ctx.GetId( obj );
if( !isNew )
{ d.WriteRef( name, id ); return; }
if( member != null )
d.OnProp( member, name );
d.BeginScope( name, type, id );
}
else
{
if( member != null )
d.OnProp( member, name );
d.BeginScope( name, type, 0 );
}
// STRATEGY 3: COLLECTIONS
if( plan.IsCollection )
{
var list = (IEnumerable)obj;
int count = 0; // Simple count (could optimize for ICollection)
foreach( var _ in list )
count++;
d.BeginCollection( name, count );
foreach( var item in list )
SerializeRecursive( item, d, ctx, "item", null );
d.EndCollection();
}
else
{
// STRATEGY 4: STANDARD OBJECT
foreach( var prop in plan.Props )
{
var val = prop.Getter( obj );
if( prop.IsSmart )
{
d.OnProp( prop.Info, prop.Name );
d.WriteAttr( prop.Name, val?.ToString() ?? "" );
}
else
{
if( val != null )
SerializeRecursive( val, d, ctx, prop.Name, prop.Info );
}
}
}
d.EndScope();
}
}
public static class Loader
{
public static void Load( object target, IInput input )
{
if( target == null || input == null )
return;
var plan = Model.Get( target.GetType() );
// 1. HYBRID PARSE (Compact String)
// If we have a parser AND input is just a value "1,1"
if( plan.IsHybrid && input.IsLeaf && !string.IsNullOrWhiteSpace( input.Value ) )
{
try
{
var newObj = plan.Parser.From( input.Value );
ParseUtils.CopyFields( newObj, target );
return;
}
catch { }
}
// 2. STRUCTURAL MAP
foreach( var prop in plan.Props )
{
// Look for Attribute OR Child Element
var sub = input.GetAttr( prop.Name ) ?? input.GetChild( prop.Name );
// Look for Dot Notation (e.g. "Pos.X") in attributes
// (Note: This simple loop doesn't scan ALL attrs for dots,
// it relies on the caller or specific recursive logic.
// For full dot support on root, we need to iterate input attributes if possible.
// But 'Binder' below handles it if we pass the specific attr key).
if( sub != null )
{
if( prop.IsSmart )
{
if( sub.Value != null )
prop.Setter( target, ParseUtils.Convert( sub.Value, prop.Type ) );
}
else
{
var child = prop.Getter( target );
if( child == null )
{
child = Activator.CreateInstance( prop.Type );
prop.Setter( target, child );
}
Load( child, sub );
}
}
}
}
// Helper to scan all attributes on an element for "Cool.Rare" patterns
public static void LoadDotNotations( object target, IEnumerable<(string k, string v)> attrs )
{
foreach( var (k, v) in attrs )
{
if( k.Contains( '.' ) )
Binder.Apply( target, k, v );
}
}
}

View File

@ -1,47 +0,0 @@
using System;
using System.Reflection;
using System.Text;
namespace srl.Debug;
public class DebugDriver : Driver
{
private StringBuilder _sb = new();
private int _indent = 0;
public override string ToString() => _sb.ToString();
private void Line( string s ) => _sb.AppendLine( new string( ' ', _indent * 2 ) + s );
public void BeginScope( string name, Type type, int id )
{
var refStr = id > 0 ? $" #{id}" : "";
Line( $"[{name}] <{type.Name}>{refStr}" );
_indent++;
}
public void EndScope() => _indent--;
public void BeginCollection( string name, int count )
{
Line( $"[{name}] (Count: {count})" );
_indent++;
}
public void EndCollection() => _indent--;
public void WriteAttr( string name, string value )
{
Line( $"{name} = {value}" );
}
public void WriteRef( string name, int id )
{
Line( $"{name} -> See #{id}" );
}
public void OnProp( MemberInfo m, string name )
{
// Could log attributes here, e.g. [Tooltip]
}
}

View File

@ -1,131 +0,0 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Reflection;
namespace srl.Xml;
// --- INPUT ADAPTER ---
public class XmlAdapter : IInput
{
private XElement _e;
private XAttribute _a;
public XmlAdapter( XElement e ) => _e = e;
public XmlAdapter( XAttribute a ) => _a = a;
public string? Value => _e?.Value ?? _a?.Value;
public bool IsLeaf => _a != null || ( _e != null && !_e.HasElements );
public IInput? GetAttr( string name )
{
if( _e == null )
return null;
// Case-Insensitive search
var a = _e.Attribute( name ) ?? _e.Attribute( name.ToLower() );
return a != null ? new XmlAdapter( a ) : null;
}
public IInput? GetChild( string name )
{
if( _e == null )
return null;
var c = _e.Element( name ) ?? _e.Element( name.ToLower() );
return c != null ? new XmlAdapter( c ) : null;
}
// Extra helper for Dot Notation scanning
public IEnumerable<(string, string)> GetAllAttrs()
{
if( _e == null )
yield break;
foreach( var a in _e.Attributes() )
yield return (a.Name.LocalName, a.Value);
}
}
// --- OUTPUT DRIVER ---
public class XmlDriver : Driver
{
private Stack<XElement> _stack = new();
private XDocument _doc;
public XDocument Document => _doc;
public XmlDriver()
{
_doc = new XDocument();
}
public void BeginScope( string name, Type type, int id )
{
var el = new XElement( name );
// Polymorphism Metadata (if needed, e.g. <Item type="Sword">)
// el.Add(new XAttribute("_type", type.Name));
if( id > 0 )
el.Add( new XAttribute( "_id", id ) ); // DAG ID
if( _stack.Count > 0 )
_stack.Peek().Add( el );
else
_doc.Add( el );
_stack.Push( el );
}
public void EndScope() => _stack.Pop();
public void BeginCollection( string name, int count )
{
// XML doesn't strictly need array wrappers, but it helps structure
// We use the same BeginScope logic effectively
var el = new XElement( name, new XAttribute( "_count", count ) );
if( _stack.Count > 0 )
_stack.Peek().Add( el );
_stack.Push( el );
}
public void EndCollection() => _stack.Pop();
public void WriteAttr( string name, string value )
{
if( _stack.Count == 0 )
return;
_stack.Peek().Add( new XAttribute( name, value ) );
}
public void WriteRef( string name, int id )
{
var el = new XElement( name, new XAttribute( "_ref", id ) );
_stack.Peek().Add( el );
}
public void OnProp( MemberInfo m, string name ) { /* Optional: Write tooltips/comments */ }
}
// --- FACADE ---
public static class XmlSerializer
{
public static string Serialize( object obj )
{
var driver = new XmlDriver();
srl.Walker.Serialize( obj, driver );
return driver.Document.ToString();
}
public static void Deserialize( object root, string xml )
{
var doc = XDocument.Parse( xml );
var adapter = new XmlAdapter( doc.Root );
// 1. Standard Load
srl.Loader.Load( root, adapter );
// 2. Dot Notation Pass (MyBag Cool.Rare="Changed")
srl.Loader.LoadDotNotations( root, adapter.GetAllAttrs() );
}
}

View File

@ -1,16 +0,0 @@
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
namespace lib;
static public class Task
{
}

View File

@ -1,125 +0,0 @@
using System.Diagnostics;
using System.IO;
namespace test;
public record class SimpleImmutable( string Name, int Age ) : io.Timed<SimpleImmutable>;
static public class XmlFormatter2
{
public class ClassWithProperties
{
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 ClassContainsClassWithProp
{
[ser.Do]
public bool doBool = true;
[ser.ChildPropsAttribute( "ActualProperty" )]
public ClassWithProperties propHolder = new();
public string doNotSerialize = "test_do_not_serialize";
}
[ser.Ser]
public class ClassHasFields
{
public ClassContainsClassWithProp prop = new();
};
public static void Serialization()
{
lib.XmlFormatter2Cfg cfg = new()
{
};
ClassHasFields classHasFields = new()
{
prop = new()
{
propHolder = new()
{
ActualProperty = "ActualProperty_set_in_cons"
}
}
};
Debug.Assert( classHasFields.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
var memStream = new MemoryStream();
{
var xml = new lib.XmlFormatter2( cfg );
xml.Serialize( memStream, classHasFields );
}
memStream.Position = 0;
var strXml = System.Text.Encoding.UTF8.GetString( memStream.ToArray() );
var badXml = "<root>\n <prop doBool=\"True\" />\n</root>";
/*
<root _.t="test.XmlFormatter2+ClassHasFields" _.version.="2">
<prop doBool="True">
<propHolder ActualProperty="ActualProperty_set_in_cons" ActualProperty_NotSerialized="ActualProperty_NotSerialized" ActualProperty="ActualProperty_set_in_cons" />
</prop>
</root>*/
Debug.Assert( strXml != badXml );
memStream.Position = 0;
var classHasFields2 = new ClassHasFields();
classHasFields2.prop.propHolder.ActualProperty_NotSerialized = "ActualProperty_NotSerialized_set_in_test_01";
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "test_ActualProperty_set_inline" );
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized" );
{
var xml = new lib.XmlFormatter2( cfg );
classHasFields2 = xml.Deserialize<ClassHasFields>( memStream );
}
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
memStream.Position = 0;
var classHasFields3 = new ClassHasFields();
{
var xml = new lib.XmlFormatter2( cfg );
xml.DeserializeInto( memStream, classHasFields3 );
}
Debug.Assert( classHasFields3.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
Debug.Assert( classHasFields3.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
}
}