AutomaticVersioning
From Unify Community Wiki
Author: Serge Billault
Contents |
Download
Compatibility
. Unity versions: Unity 5.0 through Unity 2019.3.15f1 (Untested on Unity 2020+).
. Net compatibility level: 2.0 minimum - no dynamic methods.
. Unsafe code: None.
. Reflectivity: Yes.
. Networking: None.
Description
Versioning automatic generation.
. option ignore orders: Yes
. option ignore names: Yes
. accept generics: Yes
. accept nesteds: Yes
. optional hints: Yes (custom attribute [Versioning])
Usage
Copy script file in your Assets folder.
. Action required: none
. Setup required: none
. Scripting: "int version = YOUR_NAMESPACE.Versioning.Get( Versioning.OPTIONS.<options>, ...<type> or <types list>... );"
Code (Versioning.cs)
using System; using System.Collections.Generic; using System.Reflection; //************************************************************************************************ // //************************************************************************************************ namespace YOUR_NAMESPACE { //******************************************************************************************** // //******************************************************************************************** static public class CRC_32 { //**************************************************************************************** // //**************************************************************************************** private const uint m_poly = 0XEDB88320; static private readonly uint[] m_table = new uint[ 256 ]; //**************************************************************************************** // //**************************************************************************************** static CRC_32() { uint temp = 0; uint table_len = ( uint )m_table.Length; for( uint i = 0; i < table_len; ++i ) { temp = i; for( int j = 8; j > 0; --j ) { if( ( temp & 1 ) == 1 ) { temp = ( uint )( ( temp >> 1 ) ^ m_poly ); } else { temp >>= 1; } } m_table[ i ] = temp; } } //**************************************************************************************** // //**************************************************************************************** public static uint Compute( byte[] bytes ) { if( bytes == null ) return 0; uint crc = 0XFFFFFFFF; for( int i = 0, len = bytes.Length; i < len; ++i ) { byte index = ( byte )( ( ( crc ) & 0XFF ) ^ bytes[ i ] ); crc = ( uint )( ( crc >> 8 ) ^ m_table[ index ] ); } return ~crc; } //**************************************************************************************** // //**************************************************************************************** public static uint Compute( string str ) { if( string.IsNullOrEmpty( str ) ) return 0; byte[] bytes = System.Text.Encoding.ASCII.GetBytes( str ); return Compute( bytes ); } } //******************************************************************************************** // //******************************************************************************************** static public class REFLECTION { //**************************************************************************************** // //**************************************************************************************** static public bool TypeImplementInterface( Type type, Type interfaceT ) { if( type == null ) return false; if( interfaceT == null ) return false; if( type.IsInterface == true ) return false; if( interfaceT.IsInterface == false ) return false; Type[] interfaces = type.GetInterfaces(); for( int i = 0, nb_interfaces = interfaces.Length; i < nb_interfaces; ++i ) { if( object.ReferenceEquals( interfaces[ i ], interfaceT ) ) { return true; } } return false; } //**************************************************************************************** // //**************************************************************************************** static public List< Type > GetSubClassesOf( Type T, Assembly A = null ) { List< Type > result = new List< Type >( 16 ); if( T == null ) return result; if( T.IsInterface == true ) return result; Assembly[] assemblies = ( A != null ) ? new Assembly[ 1 ] { A } : ( ( AppDomain.CurrentDomain != null ) ? AppDomain.CurrentDomain.GetAssemblies() : new Assembly[ 0 ] ); for( int a = 0, nb_assemblies = assemblies.Length; a < nb_assemblies; ++a ) { Type[] types = assemblies[ a ].GetTypes(); foreach( Type type in types ) { if( type.IsSubclassOf( T ) ) result.Add( type ); } } return result; } //**************************************************************************************** // //**************************************************************************************** static public Type[] GetTypesImplentingInterface( Type interfaceT ) { List< Type > result = new List< Type >( 16 ); if( interfaceT == null ) return result.ToArray(); if( interfaceT.IsInterface == false ) return result.ToArray(); Assembly[] assemblies = ( AppDomain.CurrentDomain != null ) ? AppDomain.CurrentDomain.GetAssemblies() : new Assembly[ 0 ]; for( int a = 0, nb_assemblies = assemblies.Length; a < nb_assemblies; ++a ) { Type[] types = assemblies[ a ].GetTypes(); for( int t = 0, nb_types = types.Length; t < nb_types; ++t ) { Type type = types[ t ]; if( ( type.IsInterface == false ) && ( TypeImplementInterface( type, interfaceT ) ) ) { result.Add( type ); } } } return result.ToArray(); } } //******************************************************************************************** // //******************************************************************************************** [ AttributeUsage( AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field, AllowMultiple = false, Inherited = true ) ] public class VersioningAttribute : Attribute {} //******************************************************************************************** // //******************************************************************************************** public class Versioning : IComparer< FieldInfo > { //**************************************************************************************** // //**************************************************************************************** [ System.Flags ] public enum OPTIONS { STRICT = 0X0, // any change impact version number IGNORE_NAMES = 0X1, // changing names do not impact version number IGNORE_ORDER = 0X2, // changing order do not impact version number IGNORE_ALL = 0X3 // only adding / removing impact version number } //**************************************************************************************** // //**************************************************************************************** static public int Get( OPTIONS opts, Type type ) { return new Versioning( opts, type ).m_crc; } static public int Get( OPTIONS opts, params Type[] types ) { return new Versioning( opts, types ).m_crc; } //**************************************************************************************** // //**************************************************************************************** public class Dependency { public Type m_type = null; public readonly List< FieldInfo > m_fields = new List< FieldInfo >(); public Dependency( Type type ) { m_type = type; } } //**************************************************************************************** // //**************************************************************************************** public class Scope : List< Type > { new public void Add( Type type ) { if( Contains( type ) == false ) base.Add( type ); } } //**************************************************************************************** // //**************************************************************************************** public const string SEM_ARRAY_START = "["; public const char SEM_ARRAY_END = ']'; public const string SEM_OBJECT_START = "{"; public const char SEM_OBJECT_END = '}'; public const char SEM_SEP = ','; public const char SEM_SPACE = ' '; //**************************************************************************************** // //**************************************************************************************** private readonly List< Dependency > m_deps = new List< Dependency >(); private int m_crc = 0; private OPTIONS m_opts = OPTIONS.STRICT; //**************************************************************************************** // //**************************************************************************************** private Versioning( OPTIONS opts, Type type ) { m_opts = opts; Build( new Type[] { type } ); } private Versioning( OPTIONS opts, params Type[] types ) { m_opts = opts; Build( types ); } private void Build ( params Type[] types ) { BuildManifest( types ); ComputeCrc(); } //**************************************************************************************** // //**************************************************************************************** private void Clear() { m_deps.Clear(); m_crc = 0; } //**************************************************************************************** // //**************************************************************************************** private bool AcceptType( Type type ) { if( type == null ) return false; if( type == typeof( object ) ) return false; if( type.GetCustomAttribute< VersioningAttribute >() != null ) return true; if( type.IsInterface == true ) return false; if( type.IsAbstract == true ) return false; if( type.IsSealed == true ) return false; if( type.IsGenericType == true ) return false; if( type.IsSerializable == false ) return false; return true; } //**************************************************************************************** // //**************************************************************************************** private bool AcceptType( Type type, Type[] exceptions ) { if( exceptions != null ) { for( int e = 0, count = exceptions.Length; e < count; ++e ) { Type exception_t = exceptions[ e ]; if( ( object.ReferenceEquals( type, exception_t ) ) || ( REFLECTION.TypeImplementInterface( type, exception_t ) ) ) { return true; } } } return AcceptType( type ); } //**************************************************************************************** // //**************************************************************************************** private void BuildManifest( params Type[] Types ) { //************************************************************************************ // //************************************************************************************ Clear(); //************************************************************************************ // //************************************************************************************ Scope scope = new Scope(); foreach( Type type in Types ) { if( type.IsInterface ) { Type[] types = REFLECTION.GetTypesImplentingInterface( type ); for( int impl = 0, nb_impls = types.Length; impl < nb_impls; ++impl ) { Type implementation = types[ impl ]; if( AcceptType( implementation, Types ) ) { scope.Add ( implementation ); } } } else { if( AcceptType( type, Types ) ) { scope.Add ( type ); } } } //************************************************************************************ // //************************************************************************************ for( int cur = 0; cur < scope.Count; ++cur ) { //******************************************************************************** // //******************************************************************************** Type cur_scope = scope[ cur ]; if( cur_scope.BaseType != null ) { if( AcceptType( cur_scope.BaseType, Types ) ) { scope.Add ( cur_scope.BaseType ); } } //******************************************************************************** // //******************************************************************************** Type[] generics = cur_scope.GetGenericArguments(); for( int g = 0, nb_generics = generics.Length; g < nb_generics; ++g ) { Type generic = generics[ g ]; if( AcceptType( generic, Types ) ) { scope.Add ( generic ); } } //******************************************************************************** // //******************************************************************************** Type[] nesteds = cur_scope.GetNestedTypes( ( BindingFlags )( -1 ) ); for( int n = 0, nb_nested = nesteds.Length; n < nb_nested; ++n ) { Type nested = nesteds[ n ]; if( AcceptType( nested, Types ) ) { scope.Add ( nested ); } } //******************************************************************************** // //******************************************************************************** Dependency dep = new Dependency( cur_scope ); FieldInfo[] fields = dep.m_type.GetFields( ( BindingFlags )( -1 ) ); m_deps.Add( dep ); for( int f = 0, nb_infos = fields.Length; f < nb_infos; ++f ) { FieldInfo field = fields[ f ]; if( field.FieldType.IsSerializable ) { dep.m_fields.Add( field ); } if( AcceptType( field.FieldType, Types ) ) { scope.Add ( field.FieldType ); } } } } //**************************************************************************************** // //**************************************************************************************** public int Compare( FieldInfo l, FieldInfo r ) { bool ignore_names = ( ( m_opts & OPTIONS.IGNORE_NAMES ) != 0 ); string l_name = ignore_names ? l.FieldType.FullName : l.FieldType.FullName + l.Name; string r_name = ignore_names ? r.FieldType.FullName : r.FieldType.FullName + r.Name; return string.Compare( l_name, r_name, StringComparison.Ordinal ); } //**************************************************************************************** // //**************************************************************************************** private string BuildDependencyOutput( Dependency dep ) { List< FieldInfo > fields = dep.m_fields; int fields_cnt = fields.Count; int last = ( fields_cnt > 0 ) ? fields_cnt - 1 : 0; if( ( m_opts & OPTIONS.IGNORE_ORDER ) != 0 ) { fields.Sort( this ); } string output = "\r\n\t" + SEM_OBJECT_START; output += "\r\n\t\t" + dep.m_type.Name; for( int fld = 0; fld < fields_cnt; ++fld ) { FieldInfo field = fields[ fld ]; string name = ( ( m_opts & OPTIONS.IGNORE_NAMES ) != 0 ) ? string.Empty : SEM_SPACE + field.Name; output += "\r\n\t\t* " + field.FieldType.FullName + name; if( fld != last ) output += SEM_SEP; } output += "\r\n\t" + SEM_OBJECT_END; return output; } //**************************************************************************************** // //**************************************************************************************** private void ComputeCrc() { string output = SEM_ARRAY_START; for( int dependency = 0; dependency < m_deps.Count; ++dependency ) { Dependency dep = m_deps[ dependency ]; output += BuildDependencyOutput( dep ); if( dependency != ( m_deps.Count - 1 ) ) output += SEM_SEP; } output += "\r\n" + SEM_ARRAY_END; m_crc = ( int )CRC_32.Compute( output ); #if false UnityEngine.Debug.Log( output ); #endif } } }